1
// SPDX-License-Identifier: GPL-3.0-or-later
2
3
-// NOT TO BE USED BY USERS
4
-#define DICTIONARY_FLAG_EXCLUSIVE_ACCESS (1 << 28) // there is only one thread accessing the dictionary
5
-#define DICTIONARY_FLAG_DESTROYED (1 << 29) // this dictionary has been destroyed
6
-#define DICTIONARY_FLAG_DEFER_ALL_DELETIONS (1 << 30) // defer all deletions of items in the dictionary
7
-
8
-// our reserved flags that cannot be set by users
9
-#define DICTIONARY_FLAGS_RESERVED (DICTIONARY_FLAG_EXCLUSIVE_ACCESS|DICTIONARY_FLAG_DESTROYED|DICTIONARY_FLAG_DEFER_ALL_DELETIONS)
10
-
3
#define DICTIONARY_INTERNALS
4
5
#include "../libnetdata.h"
6
#include <Judy.h>
7
16
-typedef enum name_value_flags {
17
- NAME_VALUE_FLAG_NONE = 0,
18
- NAME_VALUE_FLAG_NAME_IS_ALLOCATED = (1 << 0), // the name pointer is a STRING
19
- NAME_VALUE_FLAG_DELETED = (1 << 1), // this item is deleted, so it is not available for traversal
20
- NAME_VALUE_FLAG_NEW_OR_UPDATED = (1 << 2), // this item is new or just updated (used by the react callback)
8
+// runtime flags of the dictionary - must be checked with atomics
9
+typedef enum {
10
+ DICT_FLAG_NONE = 0,
11
+ DICT_FLAG_DESTROYED = (1 << 0), // this dictionary has been destroyed
12
+} DICT_FLAGS;
13
+
14
+#define dict_flag_check(dict, flag) (__atomic_load_n(&((dict)->flags), __ATOMIC_SEQ_CST) & (flag))
15
+#define dict_flag_set(dict, flag) __atomic_or_fetch(&((dict)->flags), flag, __ATOMIC_SEQ_CST)
16
+#define dict_flag_clear(dict, flag) __atomic_and_fetch(&((dict)->flags), ~(flag), __ATOMIC_SEQ_CST)
17
+
18
+// flags macros
19
+#define is_dictionary_destroyed(dict) dict_flag_check(dict, DICT_FLAG_DESTROYED)
20
+
21
+// configuration options macros
22
+#define is_dictionary_single_threaded(dict) ((dict)->options & DICT_OPTION_SINGLE_THREADED)
23
+#define is_view_dictionary(dict) ((dict)->master)
24
+#define is_master_dictionary(dict) (!is_view_dictionary(dict))
25
+
26
+typedef enum item_options {
27
+ ITEM_OPTION_NONE = 0,
28
+ ITEM_OPTION_ALLOCATED_NAME = (1 << 0), // the name pointer is a STRING
29
+
30
+ // IMPORTANT: This is 1-bit - to add more change ITEM_OPTIONS_BITS
31
+} ITEM_OPTIONS;
32
+
33
+typedef enum item_flags {
34
+ ITEM_FLAG_NONE = 0,
35
+ ITEM_FLAG_DELETED = (1 << 0), // this item is deleted, so it is not available for traversal
36
+
37
+ // IMPORTANT: This is 8-bit
38
+} ITEM_FLAGS;
39
+
40
+#define item_flag_check(item, flag) (__atomic_load_n(&((item)->flags), __ATOMIC_SEQ_CST) & (flag))
41
+#define item_flag_set(item, flag) __atomic_or_fetch(&((item)->flags), flag, __ATOMIC_SEQ_CST)
42
+#define item_flag_clear(item, flag) __atomic_and_fetch(&((item)->flags), ~(flag), __ATOMIC_SEQ_CST)
43
+
44
+#define item_shared_flag_check(item, flag) (__atomic_load_n(&((item)->shared->flags), __ATOMIC_SEQ_CST) & (flag))
45
+#define item_shared_flag_set(item, flag) __atomic_or_fetch(&((item)->shared->flags), flag, __ATOMIC_SEQ_CST)
46
+#define item_shared_flag_clear(item, flag) __atomic_and_fetch(&((item)->shared->flags), ~(flag), __ATOMIC_SEQ_CST)
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
22
- // IMPORTANT: IF YOU ADD ANOTHER FLAG, YOU NEED TO ALLOCATE ANOTHER BIT TO FLAGS IN NAME_VALUE !!!
23
-} NAME_VALUE_FLAGS;
61
62
/*
63
* Every item in the dictionary has the following structure.
64
*/
65
29
-typedef struct name_value {
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
#endif
83
34
- struct name_value *next; // a double linked list to allow fast insertions and deletions
35
- struct name_value *prev;
84
+ DICTIONARY_ITEM_SHARED *shared;
85
37
- uint32_t refcount; // the reference counter
38
- uint32_t value_len:29; // the size of the value (assumed binary)
39
- uint8_t flags:3; // the flags for this item
86
+ struct dictionary_item *next; // a double linked list to allow fast insertions and deletions
87
+ struct dictionary_item *prev;
88
41
- void *value; // the value of the dictionary item
89
union {
43
- STRING *string_name; // the name of the dictionary item
44
- char *caller_name; // the user supplied string pointer
90
+ STRING *string_name; // the name of the dictionary item
91
+ char *caller_name; // the user supplied string pointer
92
+// void *key_ptr; // binary key pointer
93
};
46
-} NAME_VALUE;
94
48
-struct dictionary {
49
-#ifdef NETDATA_INTERNAL_CHECKS
50
- const char *creation_function;
51
- const char *creation_file;
52
- size_t creation_line;
53
-#endif
95
+ // the order of the following items is important!
96
+ // The total of their storage should be 64-bits
97
+
98
+ REFCOUNT refcount; // the private reference counter
99
55
- DICTIONARY_FLAGS flags; // the flags of the dictionary
100
+ KEY_LEN_TYPE key_len:KEY_LEN_BITS; // the size of key indexed (for strings, including the null terminator)
101
+ // this is (2^23 - 1) = 8.388.607 bytes max key length.
102
57
- NAME_VALUE *first_item; // the double linked list base pointers
58
- NAME_VALUE *last_item;
103
+ ITEM_OPTIONS options:ITEM_OPTIONS_BITS; // permanent configuration options
104
+ // (no atomic operations on this - they never change)
105
60
- Pvoid_t JudyHSArray; // the hash table
106
+ ITEM_FLAGS_TYPE flags; // runtime changing flags for this item (atomic operations on this)
107
+ // cannot be a bit field because of atomics.
108
+};
109
62
- netdata_rwlock_t rwlock; // the r/w lock when DICTIONARY_FLAG_SINGLE_THREADED is not set
110
+struct dictionary_hooks {
111
+ REFCOUNT links;
112
+ usec_t last_master_deletion_us;
113
64
- void (*ins_callback)(const char *name, void *value, void *data);
114
+ void (*ins_callback)(const DICTIONARY_ITEM *item, void *value, void *data);
115
void *ins_callback_data;
116
67
- void (*react_callback)(const char *name, void *value, void *data);
117
+ bool (*conflict_callback)(const DICTIONARY_ITEM *item, void *old_value, void *new_value, void *data);
118
+ void *conflict_callback_data;
119
+
120
+ void (*react_callback)(const DICTIONARY_ITEM *item, void *value, void *data);
121
void *react_callback_data;
122
70
- void (*del_callback)(const char *name, void *value, void *data);
123
+ void (*del_callback)(const DICTIONARY_ITEM *item, void *value, void *data);
124
void *del_callback_data;
125
+};
126
73
- void (*conflict_callback)(const char *name, void *old_value, void *new_value, void *data);
74
- void *conflict_callback_data;
127
+struct dictionary_stats dictionary_stats_category_other = {
128
+ .name = "other",
129
+};
130
76
- size_t version; // the current version of the dictionary
77
- size_t inserts; // how many index insertions have been performed
78
- size_t deletes; // how many index deletions have been performed
79
- size_t searches; // how many index searches have been performed
80
- size_t resets; // how many times items have reset their values
81
- size_t walkthroughs; // how many walkthroughs have been done
82
- long int memory; // how much memory the dictionary has currently allocated
83
- long int entries; // how many items are currently in the index (the linked list may have more)
84
- long int referenced_items; // how many items of the dictionary are currently being used by 3rd parties
85
- long int pending_deletion_items; // how many items of the dictionary have been deleted, but have not been removed yet
86
- int readers; // how many readers are currently using the dictionary
87
- int writers; // how many writers are currently using the dictionary
88
-
89
- size_t scratchpad_size; // the size of the scratchpad in bytes
90
- uint8_t scratchpad[]; // variable size scratchpad requested by the caller
131
+struct dictionary {
132
+#ifdef NETDATA_INTERNAL_CHECKS
133
+ const char *creation_function;
134
+ const char *creation_file;
135
+ size_t creation_line;
136
+#endif
137
+
138
+ usec_t last_gc_run_us;
139
+ DICT_OPTIONS options; // the configuration flags of the dictionary (they never change - no atomics)
140
+ DICT_FLAGS flags; // run time flags for the dictionary (they change all the time - atomics needed)
141
+
142
+ struct { // support for multiple indexing engines
143
+ Pvoid_t JudyHSArray; // the hash table
144
+ netdata_rwlock_t rwlock; // protect the index
145
+ } index;
146
+
147
+ struct {
148
+ DICTIONARY_ITEM *list; // the double linked list of all items in the dictionary
149
+ netdata_rwlock_t rwlock; // protect the linked-list
150
+ pid_t writer_pid; // the gettid() of the writer
151
+ size_t writer_depth; // nesting of write locks
152
+ } items;
153
+
154
+ struct dictionary_hooks *hooks; // pointer to external function callbacks to be called at certain points
155
+ struct dictionary_stats *stats; // statistics data, when DICT_OPTION_STATS is set
156
+
157
+ DICTIONARY *master; // the master dictionary
158
+ DICTIONARY *next; // linked list for delayed destruction (garbage collection of whole dictionaries)
159
+
160
+ size_t version; // the current version of the dictionary
161
+ // it is incremented when:
162
+ // - item added
163
+ // - item removed
164
+ // - item value reset
165
+ // - conflict callback returns true
166
+ // - function dictionary_version_increment() is called
167
+
168
+ long int entries; // how many items are currently in the index (the linked list may have more)
169
+ long int referenced_items; // how many items of the dictionary are currently being used by 3rd parties
170
+ long int pending_deletion_items; // how many items of the dictionary have been deleted, but have not been removed yet
171
};
172
93
-static inline void linkedlist_namevalue_unlink_unsafe(DICTIONARY *dict, NAME_VALUE *nv);
94
-static size_t namevalue_destroy_unsafe(DICTIONARY *dict, NAME_VALUE *nv);
95
-static inline const char *namevalue_get_name(NAME_VALUE *nv);
173
+// forward definitions of functions used in reverse order in the code
174
+static void garbage_collect_pending_deletes(DICTIONARY *dict);
175
+static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item);
176
+static size_t item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item);
177
+static inline const char *item_get_name(const DICTIONARY_ITEM *item);
178
+static bool item_is_not_referenced_and_can_be_removed(DICTIONARY *dict, DICTIONARY_ITEM *item);
179
+static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *item);
180
+static void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item);
181
+
182
+#define ITEM_OK 0
183
+#define ITEM_MARKED_FOR_DELETION (-1) // the item is marked for deletion
184
+#define ITEM_IS_CURRENTLY_BEING_DELETED (-2) // the item is currently being deleted
185
+#define item_check_and_acquire(dict, item) (item_check_and_acquire_advanced(dict, item, false) == ITEM_OK)
186
+static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock);
187
188
// ----------------------------------------------------------------------------
98
-// callbacks registration
189
+// memory statistics
190
100
-void dictionary_register_insert_callback(DICTIONARY *dict, void (*ins_callback)(const char *name, void *value, void *data), void *data) {
101
- dict->ins_callback = ins_callback;
102
- dict->ins_callback_data = data;
191
+static inline void DICTIONARY_STATS_PLUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
192
+ if(key_size)
193
+ __atomic_fetch_add(&dict->stats->memory.indexed, (long)key_size, __ATOMIC_RELAXED);
194
+
195
+ if(item_size)
196
+ __atomic_fetch_add(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
197
+
198
+ if(value_size)
199
+ __atomic_fetch_add(&dict->stats->memory.values, (long)value_size, __ATOMIC_RELAXED);
200
}
201
+static inline void DICTIONARY_STATS_MINUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
202
+ if(key_size)
203
+ __atomic_fetch_sub(&dict->stats->memory.indexed, (long)key_size, __ATOMIC_RELAXED);
204
105
-void dictionary_register_delete_callback(DICTIONARY *dict, void (*del_callback)(const char *name, void *value, void *data), void *data) {
106
- dict->del_callback = del_callback;
107
- dict->del_callback_data = data;
205
+ if(item_size)
206
+ __atomic_fetch_sub(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
207
+
208
+ if(value_size)
209
+ __atomic_fetch_sub(&dict->stats->memory.values, (long)value_size, __ATOMIC_RELAXED);
210
}
211
110
-void dictionary_register_conflict_callback(DICTIONARY *dict, void (*conflict_callback)(const char *name, void *old_value, void *new_value, void *data), void *data) {
111
- dict->conflict_callback = conflict_callback;
112
- dict->conflict_callback_data = data;
212
+// ----------------------------------------------------------------------------
213
+// callbacks registration
214
+
215
+static inline void dictionary_hooks_allocate(DICTIONARY *dict) {
216
+ if(dict->hooks) return;
217
+
218
+ dict->hooks = callocz(1, sizeof(struct dictionary_hooks));
219
+ dict->hooks->links = 1;
220
+
221
+ DICTIONARY_STATS_PLUS_MEMORY(dict, 0, sizeof(struct dictionary_hooks), 0);
222
}
223
115
-void dictionary_register_react_callback(DICTIONARY *dict, void (*react_callback)(const char *name, void *value, void *data), void *data) {
116
- dict->react_callback = react_callback;
117
- dict->react_callback_data = data;
224
+static inline size_t dictionary_hooks_free(DICTIONARY *dict) {
225
+ if(!dict->hooks) return 0;
226
+
227
+ REFCOUNT links = __atomic_sub_fetch(&dict->hooks->links, 1, __ATOMIC_SEQ_CST);
228
+ if(links == 0) {
229
+ freez(dict->hooks);
230
+ dict->hooks = NULL;
231
+
232
+ DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(struct dictionary_hooks), 0);
233
+ return sizeof(struct dictionary_hooks);
234
+ }
235
+
236
+ return 0;
237
}
238
120
-// ----------------------------------------------------------------------------
121
-// dictionary statistics maintenance
239
+void dictionary_register_insert_callback(DICTIONARY *dict, void (*ins_callback)(const DICTIONARY_ITEM *item, void *value, void *data), void *data) {
240
+ if(unlikely(is_view_dictionary(dict)))
241
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
242
123
-long int dictionary_stats_allocated_memory(DICTIONARY *dict) {
124
- if(unlikely(!dict)) return 0;
125
- return dict->memory;
243
+ dictionary_hooks_allocate(dict);
244
+ dict->hooks->ins_callback = ins_callback;
245
+ dict->hooks->ins_callback_data = data;
246
}
127
-long int dictionary_stats_entries(DICTIONARY *dict) {
128
- if(unlikely(!dict)) return 0;
129
- return dict->entries;
247
+
248
+void dictionary_register_conflict_callback(DICTIONARY *dict, bool (*conflict_callback)(const DICTIONARY_ITEM *item, void *old_value, void *new_value, void *data), void *data) {
249
+ if(unlikely(is_view_dictionary(dict)))
250
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
251
+
252
+ dictionary_hooks_allocate(dict);
253
+ dict->hooks->conflict_callback = conflict_callback;
254
+ dict->hooks->conflict_callback_data = data;
255
}
131
-size_t dictionary_stats_version(DICTIONARY *dict) {
132
- if(unlikely(!dict)) return 0;
133
- return dict->version;
256
+
257
+void dictionary_register_react_callback(DICTIONARY *dict, void (*react_callback)(const DICTIONARY_ITEM *item, void *value, void *data), void *data) {
258
+ if(unlikely(is_view_dictionary(dict)))
259
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
260
+
261
+ dictionary_hooks_allocate(dict);
262
+ dict->hooks->react_callback = react_callback;
263
+ dict->hooks->react_callback_data = data;
264
}
135
-size_t dictionary_stats_searches(DICTIONARY *dict) {
136
- if(unlikely(!dict)) return 0;
137
- return dict->searches;
265
+
266
+void dictionary_register_delete_callback(DICTIONARY *dict, void (*del_callback)(const DICTIONARY_ITEM *item, void *value, void *data), void *data) {
267
+ if(unlikely(is_view_dictionary(dict)))
268
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
269
+
270
+ dictionary_hooks_allocate(dict);
271
+ dict->hooks->del_callback = del_callback;
272
+ dict->hooks->del_callback_data = data;
273
}
139
-size_t dictionary_stats_inserts(DICTIONARY *dict) {
274
+
275
+// ----------------------------------------------------------------------------
276
+// dictionary statistics API
277
+
278
+size_t dictionary_version(DICTIONARY *dict) {
279
if(unlikely(!dict)) return 0;
141
- return dict->inserts;
280
+
281
+ // this is required for views to return the right number
282
+ garbage_collect_pending_deletes(dict);
283
+
284
+ return __atomic_load_n(&dict->version, __ATOMIC_SEQ_CST);
285
}
143
-size_t dictionary_stats_deletes(DICTIONARY *dict) {
286
+size_t dictionary_entries(DICTIONARY *dict) {
287
if(unlikely(!dict)) return 0;
145
- return dict->deletes;
288
+
289
+ // this is required for views to return the right number
290
+ garbage_collect_pending_deletes(dict);
291
+
292
+ long int entries = __atomic_load_n(&dict->entries, __ATOMIC_SEQ_CST);
293
+ if(entries < 0)
294
+ fatal("DICTIONARY: entries is negative: %ld", entries);
295
+
296
+ return entries;
297
}
147
-size_t dictionary_stats_resets(DICTIONARY *dict) {
298
+size_t dictionary_referenced_items(DICTIONARY *dict) {
299
if(unlikely(!dict)) return 0;
149
- return dict->resets;
300
+
301
+ long int referenced_items = __atomic_load_n(&dict->referenced_items, __ATOMIC_SEQ_CST);
302
+ if(referenced_items < 0)
303
+ fatal("DICTIONARY: referenced items is negative: %ld", referenced_items);
304
+
305
+ return referenced_items;
306
}
151
-size_t dictionary_stats_walkthroughs(DICTIONARY *dict) {
307
+
308
+long int dictionary_stats_for_registry(DICTIONARY *dict) {
309
if(unlikely(!dict)) return 0;
153
- return dict->walkthroughs;
310
+ return (dict->stats->memory.indexed + dict->stats->memory.dict);
311
}
155
-size_t dictionary_stats_referenced_items(DICTIONARY *dict) {
156
- if(unlikely(!dict)) return 0;
157
- return __atomic_load_n(&dict->referenced_items, __ATOMIC_SEQ_CST);
312
+void dictionary_version_increment(DICTIONARY *dict) {
313
+ __atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST);
314
}
315
316
+// ----------------------------------------------------------------------------
317
+// internal statistics API
318
+
319
static inline void DICTIONARY_STATS_SEARCHES_PLUS1(DICTIONARY *dict) {
161
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
162
- dict->searches++;
163
- }
164
- else {
165
- __atomic_fetch_add(&dict->searches, 1, __ATOMIC_RELAXED);
166
- }
320
+ __atomic_fetch_add(&dict->stats->ops.searches, 1, __ATOMIC_RELAXED);
321
}
168
-static inline void DICTIONARY_STATS_ENTRIES_PLUS1(DICTIONARY *dict, size_t size) {
169
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
322
+static inline void DICTIONARY_ENTRIES_PLUS1(DICTIONARY *dict) {
323
+ // statistics
324
+ __atomic_fetch_add(&dict->stats->items.entries, 1, __ATOMIC_RELAXED);
325
+ __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
326
+ __atomic_fetch_add(&dict->stats->ops.inserts, 1, __ATOMIC_RELAXED);
327
+
328
+ if(unlikely(is_dictionary_single_threaded(dict))) {
329
dict->version++;
171
- dict->inserts++;
330
dict->entries++;
173
- dict->memory += (long)size;
331
+ dict->referenced_items++;
332
+
333
}
334
else {
335
__atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST);
177
- __atomic_fetch_add(&dict->inserts, 1, __ATOMIC_RELAXED);
178
- __atomic_fetch_add(&dict->entries, 1, __ATOMIC_RELAXED);
179
- __atomic_fetch_add(&dict->memory, (long)size, __ATOMIC_RELAXED);
336
+ __atomic_fetch_add(&dict->entries, 1, __ATOMIC_SEQ_CST);
337
+ __atomic_fetch_add(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
338
}
339
}
182
-static inline void DICTIONARY_STATS_ENTRIES_MINUS1(DICTIONARY *dict) {
183
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
340
+static inline void DICTIONARY_ENTRIES_MINUS1(DICTIONARY *dict) {
341
+ // statistics
342
+ __atomic_fetch_add(&dict->stats->ops.deletes, 1, __ATOMIC_RELAXED);
343
+ __atomic_fetch_sub(&dict->stats->items.entries, 1, __ATOMIC_RELAXED);
344
+
345
+ if(unlikely(is_dictionary_single_threaded(dict))) {
346
dict->version++;
185
- dict->deletes++;
347
dict->entries--;
348
}
349
else {
350
__atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST);
190
- __atomic_fetch_add(&dict->deletes, 1, __ATOMIC_RELAXED);
191
- __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_RELAXED);
192
- }
193
-}
194
-static inline void DICTIONARY_STATS_ENTRIES_MINUS_MEMORY(DICTIONARY *dict, size_t size) {
195
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
196
- dict->memory -= (long)size;
197
- }
198
- else {
199
- __atomic_fetch_sub(&dict->memory, (long)size, __ATOMIC_RELAXED);
351
+ __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_SEQ_CST);
352
}
353
}
202
-static inline void DICTIONARY_STATS_VALUE_RESETS_PLUS1(DICTIONARY *dict, size_t oldsize, size_t newsize) {
203
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
354
+static inline void DICTIONARY_VALUE_RESETS_PLUS1(DICTIONARY *dict) {
355
+ __atomic_fetch_add(&dict->stats->ops.resets, 1, __ATOMIC_RELAXED);
356
+
357
+ if(unlikely(is_dictionary_single_threaded(dict)))
358
dict->version++;
205
- dict->resets++;
206
- dict->memory += (long)newsize;
207
- dict->memory -= (long)oldsize;
208
- }
209
- else {
359
+ else
360
__atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST);
211
- __atomic_fetch_add(&dict->resets, 1, __ATOMIC_RELAXED);
212
- __atomic_fetch_add(&dict->memory, (long)newsize, __ATOMIC_RELAXED);
213
- __atomic_fetch_sub(&dict->memory, (long)oldsize, __ATOMIC_RELAXED);
214
- }
361
}
216
-
362
+static inline void DICTIONARY_STATS_TRAVERSALS_PLUS1(DICTIONARY *dict) {
363
+ __atomic_fetch_add(&dict->stats->ops.traversals, 1, __ATOMIC_RELAXED);
364
+}
365
static inline void DICTIONARY_STATS_WALKTHROUGHS_PLUS1(DICTIONARY *dict) {
218
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
219
- dict->walkthroughs++;
220
- }
221
- else {
222
- __atomic_fetch_add(&dict->walkthroughs, 1, __ATOMIC_RELAXED);
223
- }
366
+ __atomic_fetch_add(&dict->stats->ops.walkthroughs, 1, __ATOMIC_RELAXED);
367
+}
368
+static inline void DICTIONARY_STATS_CHECK_SPINS_PLUS(DICTIONARY *dict, size_t count) {
369
+ __atomic_fetch_add(&dict->stats->spin_locks.use, count, __ATOMIC_RELAXED);
370
+}
371
+static inline void DICTIONARY_STATS_INSERT_SPINS_PLUS(DICTIONARY *dict, size_t count) {
372
+ __atomic_fetch_add(&dict->stats->spin_locks.insert, count, __ATOMIC_RELAXED);
373
+}
374
+static inline void DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(DICTIONARY *dict) {
375
+ __atomic_fetch_add(&dict->stats->spin_locks.search, 1, __ATOMIC_RELAXED);
376
+}
377
+static inline void DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(DICTIONARY *dict) {
378
+ __atomic_fetch_add(&dict->stats->callbacks.inserts, 1, __ATOMIC_RELAXED);
379
+}
380
+static inline void DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(DICTIONARY *dict) {
381
+ __atomic_fetch_add(&dict->stats->callbacks.conflicts, 1, __ATOMIC_RELAXED);
382
+}
383
+static inline void DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(DICTIONARY *dict) {
384
+ __atomic_fetch_add(&dict->stats->callbacks.reacts, 1, __ATOMIC_RELAXED);
385
+}
386
+static inline void DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(DICTIONARY *dict) {
387
+ __atomic_fetch_add(&dict->stats->callbacks.deletes, 1, __ATOMIC_RELAXED);
388
+}
389
+static inline void DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(DICTIONARY *dict) {
390
+ __atomic_fetch_add(&dict->stats->ops.garbage_collections, 1, __ATOMIC_RELAXED);
391
+}
392
+static inline void DICTIONARY_STATS_DICT_CREATIONS_PLUS1(DICTIONARY *dict) {
393
+ __atomic_fetch_add(&dict->stats->dictionaries.active, 1, __ATOMIC_RELAXED);
394
+ __atomic_fetch_add(&dict->stats->ops.creations, 1, __ATOMIC_RELAXED);
395
+}
396
+static inline void DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(DICTIONARY *dict) {
397
+ __atomic_fetch_sub(&dict->stats->dictionaries.active, 1, __ATOMIC_RELAXED);
398
+ __atomic_fetch_add(&dict->stats->ops.destructions, 1, __ATOMIC_RELAXED);
399
+}
400
+static inline void DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(DICTIONARY *dict) {
401
+ __atomic_fetch_add(&dict->stats->dictionaries.deleted, 1, __ATOMIC_RELAXED);
402
+}
403
+static inline void DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(DICTIONARY *dict) {
404
+ __atomic_fetch_sub(&dict->stats->dictionaries.deleted, 1, __ATOMIC_RELAXED);
405
}
406
+static inline void DICTIONARY_STATS_DICT_FLUSHES_PLUS1(DICTIONARY *dict) {
407
+ __atomic_fetch_add(&dict->stats->ops.flushes, 1, __ATOMIC_RELAXED);
408
+}
409
+
410
+static inline long int DICTIONARY_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) {
411
+ __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
412
226
-static inline size_t DICTIONARY_STATS_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) {
227
- return __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
413
+ if(unlikely(is_dictionary_single_threaded(dict)))
414
+ return ++dict->referenced_items;
415
+ else
416
+ return __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
417
}
418
230
-static inline size_t DICTIONARY_STATS_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) {
231
- return __atomic_sub_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
419
+static inline long int DICTIONARY_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) {
420
+ __atomic_fetch_sub(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
421
+
422
+ if(unlikely(is_dictionary_single_threaded(dict)))
423
+ return --dict->referenced_items;
424
+ else
425
+ return __atomic_sub_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
426
}
427
234
-static inline size_t DICTIONARY_STATS_PENDING_DELETES_PLUS1(DICTIONARY *dict) {
235
- return __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST);
428
+static inline long int DICTIONARY_PENDING_DELETES_PLUS1(DICTIONARY *dict) {
429
+ __atomic_fetch_add(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELAXED);
430
+
431
+ if(unlikely(is_dictionary_single_threaded(dict)))
432
+ return ++dict->pending_deletion_items;
433
+ else
434
+ return __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST);
435
}
436
238
-static inline size_t DICTIONARY_STATS_PENDING_DELETES_MINUS1(DICTIONARY *dict) {
239
- return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST);
437
+static inline long int DICTIONARY_PENDING_DELETES_MINUS1(DICTIONARY *dict) {
438
+ __atomic_fetch_sub(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELAXED);
439
+
440
+ if(unlikely(is_dictionary_single_threaded(dict)))
441
+ return --dict->pending_deletion_items;
442
+ else
443
+ return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST);
444
}
445
242
-static inline size_t DICTIONARY_STATS_PENDING_DELETES_GET(DICTIONARY *dict) {
243
- return __atomic_load_n(&dict->pending_deletion_items, __ATOMIC_SEQ_CST);
446
+static inline long int DICTIONARY_PENDING_DELETES_GET(DICTIONARY *dict) {
447
+ if(unlikely(is_dictionary_single_threaded(dict)))
448
+ return dict->pending_deletion_items;
449
+ else
450
+ return __atomic_load_n(&dict->pending_deletion_items, __ATOMIC_SEQ_CST);
451
}
452
246
-static inline int DICTIONARY_NAME_VALUE_REFCOUNT_GET(NAME_VALUE *nv) {
247
- return __atomic_load_n(&nv->refcount, __ATOMIC_SEQ_CST);
453
+static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET(DICTIONARY *dict, DICTIONARY_ITEM *item) {
454
+ if(unlikely(dict && is_dictionary_single_threaded(dict))) // this is an exception, dict can be null
455
+ return item->refcount;
456
+ else
457
+ return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_SEQ_CST);
458
}
459
460
// ----------------------------------------------------------------------------
251
-// garbage collector
252
-// it is called every time someone gets a write lock to the dictionary
461
+// callbacks execution
462
254
-static void garbage_collect_pending_deletes_unsafe(DICTIONARY *dict) {
255
- if(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS)) return;
463
+static void dictionary_execute_insert_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) {
464
+ if(likely(!dict->hooks || !dict->hooks->ins_callback))
465
+ return;
466
257
- if(likely(!DICTIONARY_STATS_PENDING_DELETES_GET(dict))) return;
467
+ if(unlikely(is_view_dictionary(dict)))
468
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
469
259
- NAME_VALUE *nv = dict->first_item;
260
- while(nv) {
261
- if((nv->flags & NAME_VALUE_FLAG_DELETED) && DICTIONARY_NAME_VALUE_REFCOUNT_GET(nv) == 0) {
262
- NAME_VALUE *nv_next = nv->next;
470
+ internal_error(false,
471
+ "DICTIONARY: Running insert callback on item '%s' of dictionary created from %s() %zu@%s.",
472
+ item_get_name(item),
473
+ dict->creation_function,
474
+ dict->creation_line,
475
+ dict->creation_file);
476
264
- linkedlist_namevalue_unlink_unsafe(dict, nv);
265
- namevalue_destroy_unsafe(dict, nv);
477
+ DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict);
478
+ dict->hooks->ins_callback(item, item->shared->value, constructor_data?constructor_data:dict->hooks->ins_callback_data);
479
+}
480
267
- size_t pending = DICTIONARY_STATS_PENDING_DELETES_MINUS1(dict);
268
- if(!pending) break;
481
+static bool dictionary_execute_conflict_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *new_value, void *constructor_data) {
482
+ if(likely(!dict->hooks || !dict->hooks->conflict_callback))
483
+ return false;
484
270
- nv = nv_next;
271
- }
272
- else
273
- nv = nv->next;
274
- }
485
+ if(unlikely(is_view_dictionary(dict)))
486
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
487
+
488
+ internal_error(false,
489
+ "DICTIONARY: Running conflict callback on item '%s' of dictionary created from %s() %zu@%s.",
490
+ item_get_name(item),
491
+ dict->creation_function,
492
+ dict->creation_line,
493
+ dict->creation_file);
494
+
495
+ DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict);
496
+ return dict->hooks->conflict_callback(
497
+ item, item->shared->value, new_value,
498
+ constructor_data ? constructor_data : dict->hooks->conflict_callback_data);
499
}
500
277
-// ----------------------------------------------------------------------------
278
-// dictionary locks
501
+static void dictionary_execute_react_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) {
502
+ if(likely(!dict->hooks || !dict->hooks->react_callback))
503
+ return;
504
+
505
+ if(unlikely(is_view_dictionary(dict)))
506
+ fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
507
+
508
+ internal_error(false,
509
+ "DICTIONARY: Running react callback on item '%s' of dictionary created from %s() %zu@%s.",
510
+ item_get_name(item),
511
+ dict->creation_function,
512
+ dict->creation_line,
513
+ dict->creation_file);
514
280
-static inline size_t dictionary_lock_init(DICTIONARY *dict) {
281
- if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
282
- netdata_rwlock_init(&dict->rwlock);
515
+ DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict);
516
+ dict->hooks->react_callback(item, item->shared->value,
517
+ constructor_data?constructor_data:dict->hooks->react_callback_data);
518
+}
519
+
520
+static void dictionary_execute_delete_callback(DICTIONARY *dict, DICTIONARY_ITEM *item) {
521
+ if(likely(!dict->hooks || !dict->hooks->del_callback))
522
+ return;
523
+
524
+ // We may execute the delete callback on items deleted from a view,
525
+ // because we may have references to it, after the master is gone
526
+ // so, the shared structure will remain until the last reference is released.
527
284
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS)
285
- dict->flags &= ~DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
528
+ internal_error(false,
529
+ "DICTIONARY: Running delete callback on item '%s' of dictionary created from %s() %zu@%s.",
530
+ item_get_name(item),
531
+ dict->creation_function,
532
+ dict->creation_line,
533
+ dict->creation_file);
534
+
535
+ DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict);
536
+ dict->hooks->del_callback(item, item->shared->value, dict->hooks->del_callback_data);
537
+}
538
+
539
+// ----------------------------------------------------------------------------
540
+// dictionary locks
541
542
+static inline size_t dictionary_locks_init(DICTIONARY *dict) {
543
+ if(likely(!is_dictionary_single_threaded(dict))) {
544
+ netdata_rwlock_init(&dict->index.rwlock);
545
+ netdata_rwlock_init(&dict->items.rwlock);
546
return 0;
547
}
289
-
290
- // we are single threaded
291
- dict->flags |= DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
548
return 0;
549
}
550
295
-static inline size_t dictionary_lock_free(DICTIONARY *dict) {
296
- if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
297
- netdata_rwlock_destroy(&dict->rwlock);
551
+static inline size_t dictionary_locks_destroy(DICTIONARY *dict) {
552
+ if(likely(!is_dictionary_single_threaded(dict))) {
553
+ netdata_rwlock_destroy(&dict->index.rwlock);
554
+ netdata_rwlock_destroy(&dict->items.rwlock);
555
return 0;
556
}
557
return 0;
558
}
559
303
-static void dictionary_lock(DICTIONARY *dict, char rw) {
304
- if(rw == DICTIONARY_LOCK_NONE || rw == 'U') return;
560
+static inline void ll_recursive_lock_set_thread_as_writer(DICTIONARY *dict) {
561
+ pid_t expected = 0, desired = gettid();
562
+ if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
563
+ 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_SEQ_CST));
564
+}
565
306
- if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
307
- // read lock
308
- __atomic_add_fetch(&dict->readers, 1, __ATOMIC_RELAXED);
309
- }
310
- else {
311
- // write lock
312
- __atomic_add_fetch(&dict->writers, 1, __ATOMIC_RELAXED);
313
- }
566
+static inline void ll_recursive_unlock_unset_thread_writer(DICTIONARY *dict) {
567
+ pid_t expected = gettid(), desired = 0;
568
+ if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
569
+ 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_SEQ_CST));
570
+}
571
+
572
+static inline bool ll_recursive_lock_is_thread_the_writer(DICTIONARY *dict) {
573
+ pid_t tid = gettid();
574
+ return tid > 0 && tid == __atomic_load_n(&dict->items.writer_pid, __ATOMIC_SEQ_CST);
575
+}
576
315
- if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
577
+static void ll_recursive_lock(DICTIONARY *dict, char rw) {
578
+ if(unlikely(is_dictionary_single_threaded(dict)))
579
return;
580
581
+ if(ll_recursive_lock_is_thread_the_writer(dict)) {
582
+ dict->items.writer_depth++;
583
+ return;
584
+ }
585
+
586
if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
587
// read lock
320
- netdata_rwlock_rdlock(&dict->rwlock);
321
-
322
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
323
- internal_error(true, "DICTIONARY: left-over exclusive access to dictionary created by %s (%zu@%s) found", dict->creation_function, dict->creation_line, dict->creation_file);
324
- dict->flags &= ~DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
325
- }
588
+ netdata_rwlock_rdlock(&dict->items.rwlock);
589
}
590
else {
591
// write lock
329
- netdata_rwlock_wrlock(&dict->rwlock);
330
-
331
- dict->flags |= DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
592
+ netdata_rwlock_wrlock(&dict->items.rwlock);
593
+ ll_recursive_lock_set_thread_as_writer(dict);
594
}
595
}
596
335
-static void dictionary_unlock(DICTIONARY *dict, char rw) {
336
- if(rw == DICTIONARY_LOCK_NONE || rw == 'U') return;
597
+static void ll_recursive_unlock(DICTIONARY *dict, char rw) {
598
+ if(unlikely(is_dictionary_single_threaded(dict)))
599
+ return;
600
+
601
+ if(ll_recursive_lock_is_thread_the_writer(dict) && dict->items.writer_depth > 0) {
602
+ dict->items.writer_depth--;
603
+ return;
604
+ }
605
606
if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
607
// read unlock
340
- __atomic_sub_fetch(&dict->readers, 1, __ATOMIC_RELAXED);
608
+
609
+ netdata_rwlock_unlock(&dict->items.rwlock);
610
}
611
else {
612
// write unlock
344
- garbage_collect_pending_deletes_unsafe(dict);
345
- __atomic_sub_fetch(&dict->writers, 1, __ATOMIC_RELAXED);
613
+
614
+ ll_recursive_unlock_unset_thread_writer(dict);
615
+
616
+ netdata_rwlock_unlock(&dict->items.rwlock);
617
}
618
+}
619
+
620
+
621
+static inline void dictionary_index_lock_rdlock(DICTIONARY *dict) {
622
+ if(unlikely(is_dictionary_single_threaded(dict)))
623
+ return;
624
348
- if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
625
+ netdata_rwlock_rdlock(&dict->index.rwlock);
626
+}
627
+static inline void dictionary_index_lock_wrlock(DICTIONARY *dict) {
628
+ if(unlikely(is_dictionary_single_threaded(dict)))
629
return;
630
351
- if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS)
352
- dict->flags &= ~DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
631
+ netdata_rwlock_wrlock(&dict->index.rwlock);
632
+}
633
+static inline void dictionary_index_lock_unlock(DICTIONARY *dict) {
634
+ if(unlikely(is_dictionary_single_threaded(dict)))
635
+ return;
636
354
- netdata_rwlock_unlock(&dict->rwlock);
637
+ netdata_rwlock_unlock(&dict->index.rwlock);
638
}
639
640
// ----------------------------------------------------------------------------
358
-// deferred deletions
641
+// items garbage collector
642
360
-void dictionary_defer_all_deletions_unsafe(DICTIONARY *dict, char rw) {
361
- if(rw == 'r' || rw == 'R') {
362
- // read locked - no need to defer deletions
363
- ;
364
- }
365
- else {
366
- // write locked - defer deletions
367
- dict->flags |= DICTIONARY_FLAG_DEFER_ALL_DELETIONS;
368
- }
369
-}
643
+static void garbage_collect_pending_deletes(DICTIONARY *dict) {
644
+ usec_t last_master_deletion_us = dict->hooks?__atomic_load_n(&dict->hooks->last_master_deletion_us, __ATOMIC_SEQ_CST):0;
645
+ usec_t last_gc_run_us = __atomic_load_n(&dict->last_gc_run_us, __ATOMIC_SEQ_CST);
646
371
-void dictionary_restore_all_deletions_unsafe(DICTIONARY *dict, char rw) {
372
- if(rw == 'r' || rw == 'R') {
373
- // read locked - no need to defer deletions
374
- internal_error(dict->flags & DICTIONARY_FLAG_DEFER_ALL_DELETIONS, "DICTIONARY: deletions are deferred on a read lock");
375
- }
376
- else {
377
- // write locked - defer deletions
378
- if(dict->flags & DICTIONARY_FLAG_DEFER_ALL_DELETIONS)
379
- dict->flags &= ~DICTIONARY_FLAG_DEFER_ALL_DELETIONS;
647
+ bool is_view = is_view_dictionary(dict);
648
+
649
+ if(likely(!(
650
+ DICTIONARY_PENDING_DELETES_GET(dict) > 0 ||
651
+ (is_view && last_master_deletion_us > last_gc_run_us)
652
+ )))
653
+ return;
654
+
655
+ ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
656
+
657
+ __atomic_store_n(&dict->last_gc_run_us, now_realtime_usec(), __ATOMIC_SEQ_CST);
658
+
659
+ if(is_view)
660
+ dictionary_index_lock_wrlock(dict);
661
+
662
+ DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(dict);
663
+
664
+ size_t deleted = 0, pending = 0, examined = 0;
665
+ DICTIONARY_ITEM *item = dict->items.list, *item_next;
666
+ while(item) {
667
+ examined++;
668
+
669
+ // this will cleanup
670
+ item_next = item->next;
671
+ int rc = item_check_and_acquire_advanced(dict, item, is_view);
672
+
673
+ if(rc == ITEM_MARKED_FOR_DELETION) {
674
+ // we don't have got a reference
675
+
676
+ if(item_is_not_referenced_and_can_be_removed(dict, item)) {
677
+ DOUBLE_LINKED_LIST_REMOVE_UNSAFE(dict->items.list, item, prev, next);
678
+ item_free_with_hooks(dict, item);
679
+ deleted++;
680
+
681
+ pending = DICTIONARY_PENDING_DELETES_MINUS1(dict);
682
+ if (!pending)
683
+ break;
684
+ }
685
+ }
686
+ else if(rc == ITEM_IS_CURRENTLY_BEING_DELETED)
687
+ ; // do not touch this item (we haven't got a reference)
688
+
689
+ else if(rc == ITEM_OK)
690
+ item_release(dict, item);
691
+
692
+ item = item_next;
693
}
694
+
695
+ if(is_view)
696
+ dictionary_index_lock_unlock(dict);
697
+
698
+ ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
699
+
700
+ (void)deleted;
701
+ (void)examined;
702
+
703
+ internal_error(false, "DICTIONARY: garbage collected dictionary created by %s (%zu@%s), examined %zu items, deleted %zu items, still pending %zu items",
704
+ dict->creation_function, dict->creation_line, dict->creation_file, examined, deleted, pending);
705
+
706
}
707
708
// ----------------------------------------------------------------------------
724
return 0;
725
}
726
402
-static int reference_counter_increase(NAME_VALUE *nv) {
403
- int refcount = __atomic_add_fetch(&nv->refcount, 1, __ATOMIC_SEQ_CST);
404
- if(refcount == 1)
405
- fatal("DICTIONARY: request to dup item '%s' but its reference counter was zero", namevalue_get_name(nv));
406
- return refcount;
407
-}
727
+static void item_acquire(DICTIONARY *dict, DICTIONARY_ITEM *item) {
728
+ REFCOUNT refcount;
729
409
-static int reference_counter_acquire(DICTIONARY *dict, NAME_VALUE *nv) {
410
- int refcount;
411
- if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
412
- refcount = ++nv->refcount;
413
- else
414
- refcount = __atomic_add_fetch(&nv->refcount, 1, __ATOMIC_SEQ_CST);
730
+ if(unlikely(is_dictionary_single_threaded(dict))) {
731
+ refcount = ++item->refcount;
732
+ }
733
+ else {
734
+ // increment the refcount
735
+ refcount = __atomic_add_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST);
736
+ }
737
+
738
+ if(refcount <= 0) {
739
+ internal_error(
740
+ true,
741
+ "DICTIONARY: attempted to acquire item which is deleted (refcount = %d): "
742
+ "'%s' on dictionary created by %s() (%zu@%s)",
743
+ refcount - 1,
744
+ item_get_name(item),
745
+ dict->creation_function,
746
+ dict->creation_line,
747
+ dict->creation_file);
748
+
749
+ fatal(
750
+ "DICTIONARY: request to acquire item '%s', which is deleted (refcount = %d)!",
751
+ item_get_name(item),
752
+ refcount - 1);
753
+ }
754
755
if(refcount == 1) {
756
// referenced items counts number of unique items referenced
757
// so, we increase it only when refcount == 1
419
- DICTIONARY_STATS_REFERENCED_ITEMS_PLUS1(dict);
758
+ DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
759
760
// if this is a deleted item, but the counter increased to 1
761
// we need to remove it from the pending items to delete
423
- if (nv->flags & NAME_VALUE_FLAG_DELETED)
424
- DICTIONARY_STATS_PENDING_DELETES_MINUS1(dict);
762
+ if(item_flag_check(item, ITEM_FLAG_DELETED))
763
+ DICTIONARY_PENDING_DELETES_MINUS1(dict);
764
}
426
-
427
- return refcount;
765
}
766
430
-static uint32_t reference_counter_release(DICTIONARY *dict, NAME_VALUE *nv, bool can_get_write_lock) {
767
+static void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item) {
768
// this function may be called without any lock on the dictionary
432
- // or even when someone else has a write lock on the dictionary
433
- // so, we cannot check for EXCLUSIVE ACCESS
769
+ // or even when someone else has write lock on the dictionary
770
435
- uint32_t refcount;
436
- if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
437
- refcount = nv->refcount--;
438
- else
439
- refcount = __atomic_fetch_sub(&nv->refcount, 1, __ATOMIC_SEQ_CST);
771
+ bool is_deleted;
772
+ REFCOUNT refcount;
773
441
- if(refcount == 0) {
442
- internal_error(true, "DICTIONARY: attempted to release item without references: '%s' on dictionary created by %s() (%zu@%s)", namevalue_get_name(nv), dict->creation_function, dict->creation_line, dict->creation_file);
443
- fatal("DICTIONARY: attempted to release item without references: '%s'", namevalue_get_name(nv));
774
+ if(unlikely(is_dictionary_single_threaded(dict))) {
775
+ is_deleted = item->flags & ITEM_FLAG_DELETED;
776
+ refcount = --item->refcount;
777
}
778
+ else {
779
+ // get the flags before decrementing any reference counters
780
+ // (the other way around may lead to use-after-free)
781
+ is_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
782
446
- if(refcount == 1) {
447
- if((nv->flags & NAME_VALUE_FLAG_DELETED))
448
- DICTIONARY_STATS_PENDING_DELETES_PLUS1(dict);
783
+ // decrement the refcount
784
+ refcount = __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST);
785
+ }
786
+
787
+ if(refcount < 0) {
788
+ internal_error(
789
+ true,
790
+ "DICTIONARY: attempted to release item without references (refcount = %d): "
791
+ "'%s' on dictionary created by %s() (%zu@%s)",
792
+ refcount + 1,
793
+ item_get_name(item),
794
+ dict->creation_function,
795
+ dict->creation_line,
796
+ dict->creation_file);
797
+
798
+ fatal(
799
+ "DICTIONARY: attempted to release item '%s' without references (refcount = %d)",
800
+ item_get_name(item),
801
+ refcount + 1);
802
+ }
803
+
804
+ if(refcount == 0) {
805
+
806
+ if(is_deleted)
807
+ DICTIONARY_PENDING_DELETES_PLUS1(dict);
808
809
// referenced items counts number of unique items referenced
810
// so, we decrease it only when refcount == 0
452
- DICTIONARY_STATS_REFERENCED_ITEMS_MINUS1(dict);
811
+ DICTIONARY_REFERENCED_ITEMS_MINUS1(dict);
812
}
813
+}
814
455
- if(can_get_write_lock && DICTIONARY_STATS_PENDING_DELETES_GET(dict)) {
456
- // we can garbage collect now
815
+static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock) {
816
+ size_t spins = 0;
817
+ REFCOUNT refcount, desired;
818
458
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
459
- garbage_collect_pending_deletes_unsafe(dict);
460
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
461
- }
819
+ do {
820
+ spins++;
821
463
- return refcount;
464
-}
822
+ refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
823
466
-// ----------------------------------------------------------------------------
467
-// hash table
824
+ if(refcount < 0) {
825
+ // we can't use this item
826
+ return ITEM_IS_CURRENTLY_BEING_DELETED;
827
+ }
828
469
-static void hashtable_init_unsafe(DICTIONARY *dict) {
470
- dict->JudyHSArray = NULL;
471
-}
829
+ if(item_flag_check(item, ITEM_FLAG_DELETED)) {
830
+ // we can't use this item
831
+ return ITEM_MARKED_FOR_DELETION;
832
+ }
833
473
-static size_t hashtable_destroy_unsafe(DICTIONARY *dict) {
474
- if(unlikely(!dict->JudyHSArray)) return 0;
834
+ desired = refcount + 1;
835
476
- JError_t J_Error;
477
- Word_t ret = JudyHSFreeArray(&dict->JudyHSArray, &J_Error);
478
- if(unlikely(ret == (Word_t) JERR)) {
479
- error("DICTIONARY: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
480
- JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
481
- }
836
+ } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired,
837
+ false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));
838
483
- debug(D_DICTIONARY, "Dictionary: hash table freed %lu bytes", ret);
839
+ // we acquired the item
840
485
- dict->JudyHSArray = NULL;
486
- return (size_t)ret;
487
-}
841
+ if(is_view_dictionary(dict) && item_shared_flag_check(item, ITEM_FLAG_DELETED) && !item_flag_check(item, ITEM_FLAG_DELETED)) {
842
+ // but, we can't use this item
843
489
-static inline void **hashtable_insert_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
490
- internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: inserting item from the index without exclusive access to the dictionary created by %s() (%zu@%s)", dict->creation_function, dict->creation_line, dict->creation_file);
844
+ if(having_index_lock) {
845
+ // delete it from the hashtable
846
+ hashtable_delete_unsafe(dict, item_get_name(item), item->key_len, item);
847
492
- JError_t J_Error;
493
- Pvoid_t *Rc = JudyHSIns(&dict->JudyHSArray, (void *)name, name_len, &J_Error);
494
- if (unlikely(Rc == PJERR)) {
495
- fatal("DICTIONARY: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
496
- name, JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
497
- }
848
+ // mark it in our dictionary as deleted too
849
+ // this is safe to be done here, because we have got
850
+ // a reference counter on item
851
+ item_flag_set(item, ITEM_FLAG_DELETED);
852
499
- // if *Rc == 0, new item added to the array
500
- // otherwise the existing item value is returned in *Rc
853
+ DICTIONARY_ENTRIES_MINUS1(dict);
854
+
855
+ // decrement the refcount we incremented above
856
+ if (__atomic_sub_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST) == 0) {
857
+ // this is a deleted item, and we are the last one
858
+ DICTIONARY_PENDING_DELETES_PLUS1(dict);
859
+ }
860
+
861
+ // do not touch the item below this point
862
+ }
863
+ else {
864
+ // this is traversal / walkthrough
865
+ // decrement the refcount we incremented above
866
+ __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST);
867
+ }
868
+
869
+ return ITEM_MARKED_FOR_DELETION;
870
+ }
871
+
872
+ if(desired == 1)
873
+ DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
874
+
875
+ if(unlikely(spins > 2 && dict->stats))
876
+ DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, spins - 2);
877
+
878
+ return ITEM_OK; // we can use this item
879
+}
880
+
881
+// if a dictionary item can be deleted, return true, otherwise return false
882
+// we use the private reference counter
883
+static inline bool item_is_not_referenced_and_can_be_removed(DICTIONARY *dict, DICTIONARY_ITEM *item) {
884
+ // if we can set refcount to REFCOUNT_DELETING, we can delete this item
885
+
886
+ REFCOUNT expected = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
887
+ if(expected == 0 && __atomic_compare_exchange_n(&item->refcount, &expected, REFCOUNT_DELETING,
888
+ false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
889
+
890
+ // we are going to delete it
891
+ return true;
892
+ }
893
+
894
+ // we can't delete this
895
+ return false;
896
+}
897
+
898
+// if a dictionary item can be freed, return true, otherwise return false
899
+// we use the shared reference counter
900
+static inline bool item_shared_release_and_check_if_it_can_be_freed(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item) {
901
+ // if we can set refcount to REFCOUNT_DELETING, we can delete this item
902
+
903
+ REFCOUNT links = __atomic_sub_fetch(&item->shared->links, 1, __ATOMIC_SEQ_CST);
904
+ if(links == 0 && __atomic_compare_exchange_n(&item->shared->links, &links, REFCOUNT_DELETING,
905
+ false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
906
+
907
+ // we can delete it
908
+ return true;
909
+ }
910
+
911
+ // we can't delete it
912
+ return false;
913
+}
914
+
915
+
916
+// ----------------------------------------------------------------------------
917
+// hash table operations
918
+
919
+static size_t hashtable_init_unsafe(DICTIONARY *dict) {
920
+ dict->index.JudyHSArray = NULL;
921
+ return 0;
922
+}
923
+
924
+static size_t hashtable_destroy_unsafe(DICTIONARY *dict) {
925
+ if(unlikely(!dict->index.JudyHSArray)) return 0;
926
+
927
+ JError_t J_Error;
928
+ Word_t ret = JudyHSFreeArray(&dict->index.JudyHSArray, &J_Error);
929
+ if(unlikely(ret == (Word_t) JERR)) {
930
+ error("DICTIONARY: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
931
+ JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
932
+ }
933
+
934
+ debug(D_DICTIONARY, "Dictionary: hash table freed %lu bytes", ret);
935
+
936
+ dict->index.JudyHSArray = NULL;
937
+ return (size_t)ret;
938
+}
939
+
940
+static inline void **hashtable_insert_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
941
+ JError_t J_Error;
942
+ Pvoid_t *Rc = JudyHSIns(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
943
+ if (unlikely(Rc == PJERR)) {
944
+ fatal("DICTIONARY: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
945
+ name, JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
946
+ }
947
+
948
+ // if *Rc == 0, new item added to the array
949
+ // otherwise the existing item value is returned in *Rc
950
951
// we return a pointer to a pointer, so that the caller can
952
// put anything needed at the value of the index.
955
return Rc;
956
}
957
509
-static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *nv) {
510
- internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: deleting item from the index without exclusive access to the dictionary created by %s() (%zu@%s)", dict->creation_function, dict->creation_line, dict->creation_file);
511
-
512
- (void)nv;
513
- if(unlikely(!dict->JudyHSArray)) return 0;
958
+static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *item) {
959
+ (void)item;
960
+ if(unlikely(!dict->index.JudyHSArray)) return 0;
961
962
JError_t J_Error;
516
- int ret = JudyHSDel(&dict->JudyHSArray, (void *)name, name_len, &J_Error);
963
+ int ret = JudyHSDel(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
964
if(unlikely(ret == JERR)) {
965
error("DICTIONARY: Cannot delete entry with name '%s' from JudyHS, JU_ERRNO_* == %u, ID == %d", name,
966
JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
980
}
981
}
982
536
-static inline NAME_VALUE *hashtable_get_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
537
- if(unlikely(!dict->JudyHSArray)) return NULL;
983
+static inline DICTIONARY_ITEM *hashtable_get_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
984
+ if(unlikely(!dict->index.JudyHSArray)) return NULL;
985
986
DICTIONARY_STATS_SEARCHES_PLUS1(dict);
987
988
Pvoid_t *Rc;
542
- Rc = JudyHSGet(dict->JudyHSArray, (void *)name, name_len);
989
+ Rc = JudyHSGet(dict->index.JudyHSArray, (void *)name, name_len);
990
if(likely(Rc)) {
991
// found in the hash table
545
- return (NAME_VALUE *)*Rc;
992
+ return (DICTIONARY_ITEM *)*Rc;
993
}
994
else {
995
// not found in the hash table
997
}
998
}
999
553
-static inline void hashtable_inserted_name_value_unsafe(DICTIONARY *dict, void *nv) {
1000
+static inline void hashtable_inserted_item_unsafe(DICTIONARY *dict, void *item) {
1001
(void)dict;
555
- (void)nv;
1002
+ (void)item;
1003
+
1004
+ // this is called just after an item is successfully inserted to the hashtable
1005
+ // we don't need this for judy, but we may need it if we integrate more hash tables
1006
+
1007
;
1008
}
1009
1010
// ----------------------------------------------------------------------------
1011
// linked list management
1012
562
-static inline void linkedlist_namevalue_link_unsafe(DICTIONARY *dict, NAME_VALUE *nv) {
563
- internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: adding item to the linked-list without exclusive access to the dictionary created by %s() (%zu@%s)", dict->creation_function, dict->creation_line, dict->creation_file);
564
-
565
- if (unlikely(!dict->first_item)) {
566
- // we are the only ones here
567
- nv->next = NULL;
568
- nv->prev = NULL;
569
- dict->first_item = dict->last_item = nv;
570
- return;
571
- }
1013
+static inline void item_linked_list_add(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1014
+ ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
1015
573
- if(dict->flags & DICTIONARY_FLAG_ADD_IN_FRONT) {
574
- // add it at the beginning
575
- nv->prev = NULL;
576
- nv->next = dict->first_item;
577
-
578
- if (likely(nv->next)) nv->next->prev = nv;
579
- dict->first_item = nv;
580
- }
581
- else {
582
- // add it at the end
583
- nv->next = NULL;
584
- nv->prev = dict->last_item;
1016
+ if(dict->options & DICT_OPTION_ADD_IN_FRONT)
1017
+ DOUBLE_LINKED_LIST_PREPEND_UNSAFE(dict->items.list, item, prev, next);
1018
+ else
1019
+ DOUBLE_LINKED_LIST_APPEND_UNSAFE(dict->items.list, item, prev, next);
1020
586
- if (likely(nv->prev)) nv->prev->next = nv;
587
- dict->last_item = nv;
588
- }
1021
+ garbage_collect_pending_deletes(dict);
1022
+ ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1023
}
1024
591
-static inline void linkedlist_namevalue_unlink_unsafe(DICTIONARY *dict, NAME_VALUE *nv) {
592
- internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: removing item from the linked-list without exclusive access to the dictionary created by %s() (%zu@%s)", dict->creation_function, dict->creation_line, dict->creation_file);
1025
+static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1026
+ ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
1027
+
1028
+ DOUBLE_LINKED_LIST_REMOVE_UNSAFE(dict->items.list, item, prev, next);
1029
594
- if(nv->next) nv->next->prev = nv->prev;
595
- if(nv->prev) nv->prev->next = nv->next;
596
- if(dict->first_item == nv) dict->first_item = nv->next;
597
- if(dict->last_item == nv) dict->last_item = nv->prev;
1030
+ garbage_collect_pending_deletes(dict);
1031
+ ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1032
}
1033
1034
// ----------------------------------------------------------------------------
601
-// NAME_VALUE methods
1035
+// ITEM initialization and updates
1036
603
-static inline size_t namevalue_set_name(DICTIONARY *dict, NAME_VALUE *nv, const char *name, size_t name_len) {
604
- if(likely(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE)) {
605
- nv->caller_name = (char *)name;
606
- return 0;
1037
+static inline size_t item_set_name(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *name, size_t name_len) {
1038
+ if(likely(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)) {
1039
+ item->caller_name = (char *)name;
1040
+ item->key_len = name_len;
1041
+ }
1042
+ else {
1043
+ item->string_name = string_strdupz(name);
1044
+ item->key_len = string_strlen(item->string_name) + 1;
1045
+ item->options |= ITEM_OPTION_ALLOCATED_NAME;
1046
}
1047
609
- nv->string_name = string_strdupz(name);
610
- nv->flags |= NAME_VALUE_FLAG_NAME_IS_ALLOCATED;
611
- return name_len;
1048
+ return item->key_len;
1049
}
1050
614
-static inline size_t namevalue_free_name(DICTIONARY *dict, NAME_VALUE *nv) {
615
- if(unlikely(!(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE)))
616
- string_freez(nv->string_name);
1051
+static inline size_t item_free_name(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1052
+ if(likely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
1053
+ string_freez(item->string_name);
1054
618
- return 0;
1055
+ return item->key_len;
1056
}
1057
621
-static inline const char *namevalue_get_name(NAME_VALUE *nv) {
622
- if(nv->flags & NAME_VALUE_FLAG_NAME_IS_ALLOCATED)
623
- return string2str(nv->string_name);
1058
+static inline const char *item_get_name(const DICTIONARY_ITEM *item) {
1059
+ if(item->options & ITEM_OPTION_ALLOCATED_NAME)
1060
+ return string2str(item->string_name);
1061
else
625
- return nv->caller_name;
1062
+ return item->caller_name;
1063
}
1064
628
-static NAME_VALUE *namevalue_create_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *value, size_t value_len) {
629
- debug(D_DICTIONARY, "Creating name value entry for name '%s'.", name);
1065
+static DICTIONARY_ITEM *item_allocate(DICTIONARY *dict __maybe_unused, size_t *allocated_bytes, DICTIONARY_ITEM *master_item) {
1066
+ DICTIONARY_ITEM *item;
1067
631
- size_t size = sizeof(NAME_VALUE);
632
- NAME_VALUE *nv = mallocz(size);
633
- size_t allocated = size;
1068
+ size_t size = sizeof(DICTIONARY_ITEM);
1069
+ item = callocz(1, size);
1070
+ item->refcount = 1;
1071
+ *allocated_bytes += size;
1072
1073
+ if(master_item) {
1074
+ item->shared = master_item->shared;
1075
+
1076
+ if(unlikely(__atomic_add_fetch(&item->shared->links, 1, __ATOMIC_SEQ_CST) <= 1))
1077
+ fatal("DICTIONARY: attempted to link to a shared item structure that had zero references");
1078
+ }
1079
+ else {
1080
+ size = sizeof(DICTIONARY_ITEM_SHARED);
1081
+ item->shared = callocz(1, size);
1082
+ item->shared->links = 1;
1083
+ *allocated_bytes += size;
1084
+ }
1085
+
1086
+#ifdef NETDATA_INTERNAL_CHECKS
1087
+ item->dict = dict;
1088
+#endif
1089
+ return item;
1090
+}
1091
+
1092
+static DICTIONARY_ITEM *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) {
1093
#ifdef NETDATA_INTERNAL_CHECKS
636
- nv->dict = dict;
1094
+ if(unlikely(name_len > KEY_LEN_MAX))
1095
+ fatal("DICTIONARY: tried to index a key of size %zu, but the maximum acceptable is %zu", name_len, (size_t)KEY_LEN_MAX);
1096
+
1097
+ if(unlikely(value_len > VALUE_LEN_MAX))
1098
+ fatal("DICTIONARY: tried to add an item of size %zu, but the maximum acceptable is %zu", value_len, (size_t)VALUE_LEN_MAX);
1099
#endif
1100
639
- nv->refcount = 0;
640
- nv->flags = NAME_VALUE_FLAG_NONE;
641
- nv->value_len = value_len;
1101
+ size_t item_size = 0, key_size = 0, value_size = 0;
1102
+
1103
+ DICTIONARY_ITEM *item = item_allocate(dict, &item_size, master_item);
1104
+ key_size += item_set_name(dict, item, name, name_len);
1105
643
- allocated += namevalue_set_name(dict, nv, name, name_len);
1106
+ if(unlikely(is_view_dictionary(dict))) {
1107
+ // we are on a view dictionary
1108
+ // do not touch the value
1109
+ ;
1110
645
- if(likely(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE))
646
- nv->value = value;
1111
+#ifdef NETDATA_INTERNAL_CHECKS
1112
+ if(unlikely(!master_item))
1113
+ fatal("DICTIONARY: cannot add an item to a view without a master item.");
1114
+#endif
1115
+ }
1116
else {
648
- if(likely(value_len)) {
649
- if (value) {
650
- // a value has been supplied
651
- // copy it
652
- nv->value = mallocz(value_len);
653
- memcpy(nv->value, value, value_len);
1117
+ // we are on the master dictionary
1118
+
1119
+ if(likely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))
1120
+ item->shared->value = value;
1121
+ else {
1122
+ if(likely(value_len)) {
1123
+ if(value) {
1124
+ // a value has been supplied
1125
+ // copy it
1126
+ item->shared->value = mallocz(value_len);
1127
+ memcpy(item->shared->value, value, value_len);
1128
+ }
1129
+ else {
1130
+ // no value has been supplied
1131
+ // allocate a clear memory block
1132
+ item->shared->value = callocz(1, value_len);
1133
+ }
1134
+
1135
}
1136
else {
656
- // no value has been supplied
657
- // allocate a clear memory block
658
- nv->value = callocz(1, value_len);
1137
+ // the caller wants an item without any value
1138
+ item->shared->value = NULL;
1139
}
1140
}
661
- else {
662
- // the caller wants an item without any value
663
- nv->value = NULL;
664
- }
1141
+ item->shared->value_len = value_len;
1142
+ value_size += value_len;
1143
666
- allocated += value_len;
1144
+ dictionary_execute_insert_callback(dict, item, constructor_data);
1145
}
1146
669
- DICTIONARY_STATS_ENTRIES_PLUS1(dict, allocated);
1147
+ DICTIONARY_ENTRIES_PLUS1(dict);
1148
+ DICTIONARY_STATS_PLUS_MEMORY(dict, key_size, item_size, value_size);
1149
671
- if(dict->ins_callback)
672
- dict->ins_callback(namevalue_get_name(nv), nv->value, dict->ins_callback_data);
673
-
674
- return nv;
1150
+ return item;
1151
}
1152
677
-static void namevalue_reset_unsafe(DICTIONARY *dict, NAME_VALUE *nv, void *value, size_t value_len) {
678
- debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", namevalue_get_name(nv));
1153
+static void item_reset_value_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item, void *value, size_t value_len, void *constructor_data) {
1154
+ if(unlikely(is_view_dictionary(dict)))
1155
+ fatal("DICTIONARY: %s() should never be called on views.", __FUNCTION__ );
1156
680
- DICTIONARY_STATS_VALUE_RESETS_PLUS1(dict, nv->value_len, value_len);
1157
+ debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", item_get_name(item));
1158
682
- if(dict->del_callback)
683
- dict->del_callback(namevalue_get_name(nv), nv->value, dict->del_callback_data);
1159
+ DICTIONARY_VALUE_RESETS_PLUS1(dict);
1160
685
- if(likely(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE)) {
686
- debug(D_DICTIONARY, "Dictionary: linking value to '%s'", namevalue_get_name(nv));
687
- nv->value = value;
688
- nv->value_len = value_len;
1161
+ if(item->shared->value_len != value_len) {
1162
+ DICTIONARY_STATS_PLUS_MEMORY(dict, 0, 0, value_len);
1163
+ DICTIONARY_STATS_MINUS_MEMORY(dict, 0, 0, item->shared->value_len);
1164
+ }
1165
+
1166
+ dictionary_execute_delete_callback(dict, item);
1167
+
1168
+ if(likely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE)) {
1169
+ debug(D_DICTIONARY, "Dictionary: linking value to '%s'", item_get_name(item));
1170
+ item->shared->value = value;
1171
+ item->shared->value_len = value_len;
1172
}
1173
else {
691
- debug(D_DICTIONARY, "Dictionary: cloning value to '%s'", namevalue_get_name(nv));
1174
+ debug(D_DICTIONARY, "Dictionary: cloning value to '%s'", item_get_name(item));
1175
693
- void *oldvalue = nv->value;
694
- void *newvalue = NULL;
1176
+ void *old_value = item->shared->value;
1177
+ void *new_value = NULL;
1178
if(value_len) {
696
- newvalue = mallocz(value_len);
697
- if(value) memcpy(newvalue, value, value_len);
698
- else memset(newvalue, 0, value_len);
1179
+ new_value = mallocz(value_len);
1180
+ if(value) memcpy(new_value, value, value_len);
1181
+ else memset(new_value, 0, value_len);
1182
}
700
- nv->value = newvalue;
701
- nv->value_len = value_len;
1183
+ item->shared->value = new_value;
1184
+ item->shared->value_len = value_len;
1185
703
- debug(D_DICTIONARY, "Dictionary: freeing old value of '%s'", namevalue_get_name(nv));
704
- freez(oldvalue);
1186
+ debug(D_DICTIONARY, "Dictionary: freeing old value of '%s'", item_get_name(item));
1187
+ freez(old_value);
1188
}
1189
707
- if(dict->ins_callback)
708
- dict->ins_callback(namevalue_get_name(nv), nv->value, dict->ins_callback_data);
1190
+ dictionary_execute_insert_callback(dict, item, constructor_data);
1191
}
1192
711
-static size_t namevalue_destroy_unsafe(DICTIONARY *dict, NAME_VALUE *nv) {
712
- debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", namevalue_get_name(nv));
713
-
714
- if(dict->del_callback)
715
- dict->del_callback(namevalue_get_name(nv), nv->value, dict->del_callback_data);
1193
+static size_t item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1194
+ debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", item_get_name(item));
1195
717
- size_t freed = 0;
1196
+ size_t item_size = 0, key_size = 0, value_size = 0;
1197
719
- if(unlikely(!(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE))) {
720
- debug(D_DICTIONARY, "Dictionary freeing value of '%s'", namevalue_get_name(nv));
721
- freez(nv->value);
722
- freed += nv->value_len;
723
- }
1198
+ key_size += item->key_len;
1199
+ if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
1200
+ item_free_name(dict, item);
1201
725
- if(unlikely(!(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE))) {
726
- debug(D_DICTIONARY, "Dictionary freeing name '%s'", namevalue_get_name(nv));
727
- freed += namevalue_free_name(dict, nv);
728
- }
1202
+ if(item_shared_release_and_check_if_it_can_be_freed(dict, item)) {
1203
+ dictionary_execute_delete_callback(dict, item);
1204
730
- freez(nv);
731
- freed += sizeof(NAME_VALUE);
732
-
733
- DICTIONARY_STATS_ENTRIES_MINUS_MEMORY(dict, freed);
1205
+ if(unlikely(!(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))) {
1206
+ debug(D_DICTIONARY, "Dictionary freeing value of '%s'", item_get_name(item));
1207
+ freez(item->shared->value);
1208
+ item->shared->value = NULL;
1209
+ }
1210
+ value_size += item->shared->value_len;
1211
735
- return freed;
736
-}
1212
+ freez(item->shared);
1213
+ item->shared = NULL;
1214
+ item_size += sizeof(DICTIONARY_ITEM_SHARED);
1215
+ }
1216
738
-// if a dictionary item can be deleted, return true, otherwise return false
739
-static bool name_value_can_be_deleted(DICTIONARY *dict, NAME_VALUE *nv) {
740
- if(unlikely(dict->flags & DICTIONARY_FLAG_DEFER_ALL_DELETIONS))
741
- return false;
1217
+ freez(item);
1218
+ item_size += sizeof(DICTIONARY_ITEM);
1219
743
- if(unlikely(DICTIONARY_NAME_VALUE_REFCOUNT_GET(nv) > 0))
744
- return false;
1220
+ DICTIONARY_STATS_MINUS_MEMORY(dict, key_size, item_size, value_size);
1221
746
- return true;
1222
+ // we return the memory we actually freed
1223
+ return item_size + (dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE)?0:value_size;
1224
}
1225
1226
// ----------------------------------------------------------------------------
750
-// API - dictionary management
751
-#ifdef NETDATA_INTERNAL_CHECKS
752
-DICTIONARY *dictionary_create_advanced_with_trace(DICTIONARY_FLAGS flags, size_t scratchpad_size, const char *function, size_t line, const char *file) {
753
-#else
754
-DICTIONARY *dictionary_create_advanced(DICTIONARY_FLAGS flags, size_t scratchpad_size) {
755
-#endif
756
- debug(D_DICTIONARY, "Creating dictionary.");
757
-
758
- if(unlikely(flags & DICTIONARY_FLAGS_RESERVED))
759
- flags &= ~DICTIONARY_FLAGS_RESERVED;
760
-
761
- DICTIONARY *dict = callocz(1, sizeof(DICTIONARY) + scratchpad_size);
762
- size_t allocated = sizeof(DICTIONARY) + scratchpad_size;
763
-
764
- dict->scratchpad_size = scratchpad_size;
765
- dict->flags = flags;
766
- dict->first_item = dict->last_item = NULL;
767
-
768
- allocated += dictionary_lock_init(dict);
769
- allocated += reference_counter_init(dict);
770
- dict->memory = (long)allocated;
1227
+// item operations
1228
772
- hashtable_init_unsafe(dict);
1229
+static void item_shared_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1230
+ if(is_master_dictionary(dict)) {
1231
+ item_shared_flag_set(item, ITEM_FLAG_DELETED);
1232
774
-#ifdef NETDATA_INTERNAL_CHECKS
775
- dict->creation_function = function;
776
- dict->creation_file = file;
777
- dict->creation_line = line;
778
-#endif
779
-
780
- return (DICTIONARY *)dict;
1233
+ if(dict->hooks)
1234
+ __atomic_store_n(&dict->hooks->last_master_deletion_us, now_realtime_usec(), __ATOMIC_SEQ_CST);
1235
+ }
1236
}
1237
783
-void *dictionary_scratchpad(DICTIONARY *dict) {
784
- return &dict->scratchpad;
1238
+static inline void item_free_or_mark_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1239
+ if(item_is_not_referenced_and_can_be_removed(dict, item)) {
1240
+ item_shared_set_deleted(dict, item);
1241
+ item_linked_list_remove(dict, item);
1242
+ item_free_with_hooks(dict, item);
1243
+ }
1244
+ else {
1245
+ item_shared_set_deleted(dict, item);
1246
+ item_flag_set(item, ITEM_FLAG_DELETED);
1247
+ // after this point do not touch the item
1248
+ }
1249
+
1250
+ // the item is not available anymore
1251
+ DICTIONARY_ENTRIES_MINUS1(dict);
1252
}
1253
787
-size_t dictionary_destroy(DICTIONARY *dict) {
788
- if(!dict) return 0;
1254
+// this is used by traversal functions to remove the current item
1255
+// if it is deleted and it has zero references. This will eliminate
1256
+// the need for the garbage collector to kick-in later.
1257
+// Most deletions happen during traversal, so this is a nice hack
1258
+// to speed up everything!
1259
+static inline void item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(DICTIONARY *dict, DICTIONARY_ITEM *item, char rw) {
1260
+ if(rw == DICTIONARY_LOCK_WRITE) {
1261
+ bool should_be_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
1262
790
- NAME_VALUE *nv;
1263
+ item_release(dict, item);
1264
792
- debug(D_DICTIONARY, "Destroying dictionary.");
1265
+ if(should_be_deleted && item_is_not_referenced_and_can_be_removed(dict, item)) {
1266
+ // this has to be before removing from the linked list,
1267
+ // otherwise the garbage collector will also kick in!
1268
+ DICTIONARY_PENDING_DELETES_MINUS1(dict);
1269
794
- long referenced_items = 0;
795
- size_t retries = 0;
796
- do {
797
- referenced_items = __atomic_load_n(&dict->referenced_items, __ATOMIC_SEQ_CST);
798
- if (referenced_items) {
799
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
800
-
801
- // there are referenced items
802
- // delete all items individually, so that only the referenced will remain
803
- NAME_VALUE *nv_next;
804
- for (nv = dict->first_item; nv; nv = nv_next) {
805
- nv_next = nv->next;
806
- size_t refcount = DICTIONARY_NAME_VALUE_REFCOUNT_GET(nv);
807
- if (!refcount && !(nv->flags & NAME_VALUE_FLAG_DELETED))
808
- dictionary_del_unsafe(dict, namevalue_get_name(nv));
809
- }
810
-
811
- internal_error(
812
- retries == 0,
813
- "DICTIONARY: waiting (try %zu) for destruction of dictionary created from %s() %zu@%s, because it has %ld referenced items in it (%ld total).",
814
- retries + 1,
815
- dict->creation_function,
816
- dict->creation_line,
817
- dict->creation_file,
818
- referenced_items,
819
- dict->entries);
820
-
821
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
822
- sleep_usec(10000);
1270
+ item_linked_list_remove(dict, item);
1271
+ item_free_with_hooks(dict, item);
1272
}
824
- } while(referenced_items > 0 && ++retries < 10);
825
-
826
- if(referenced_items) {
827
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
1273
+ }
1274
+ else {
1275
+ // we can't do anything under this mode
1276
+ item_release(dict, item);
1277
+ }
1278
+}
1279
829
- dict->flags |= DICTIONARY_FLAG_DESTROYED;
1280
+static bool item_del(DICTIONARY *dict, const char *name, ssize_t name_len) {
1281
+ if(unlikely(!name || !*name)) {
1282
internal_error(
1283
true,
832
- "DICTIONARY: delaying destruction of dictionary created from %s() %zu@%s after %zu retries, because it has %ld referenced items in it (%ld total).",
1284
+ "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
1285
+ __FUNCTION__,
1286
dict->creation_function,
1287
dict->creation_line,
835
- dict->creation_file,
836
- retries,
837
- referenced_items,
838
- dict->entries);
1288
+ dict->creation_file);
1289
+ return false;
1290
+ }
1291
840
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
841
- return 0;
1292
+ if(unlikely(is_dictionary_destroyed(dict))) {
1293
+ internal_error(true, "DICTIONARY: attempted to dictionary_del() on a destroyed dictionary");
1294
+ return false;
1295
}
1296
844
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
1297
+ if(name_len == -1)
1298
+ name_len = (ssize_t)strlen(name) + 1; // we need the terminating null too
1299
846
- size_t freed = 0;
847
- nv = dict->first_item;
848
- while (nv) {
849
- // cache nv->next
850
- // because we are going to free nv
851
- NAME_VALUE *nv_next = nv->next;
852
- freed += namevalue_destroy_unsafe(dict, nv);
853
- nv = nv_next;
854
- // to speed up destruction, we don't
855
- // unlink nv from the linked-list here
856
- }
1300
+ debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
1301
858
- dict->first_item = NULL;
859
- dict->last_item = NULL;
1302
+ // Unfortunately, the JudyHSDel() does not return the value of the
1303
+ // item that was deleted, so we have to find it before we delete it,
1304
+ // since we need to release our structures too.
1305
861
- // destroy the dictionary
862
- freed += hashtable_destroy_unsafe(dict);
1306
+ dictionary_index_lock_wrlock(dict);
1307
864
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
865
- freed += dictionary_lock_free(dict);
866
- freed += reference_counter_free(dict);
867
- freed += sizeof(DICTIONARY) + dict->scratchpad_size;
868
- freez(dict);
1308
+ int ret;
1309
+ DICTIONARY_ITEM *item = hashtable_get_unsafe(dict, name, name_len);
1310
+ if(unlikely(!item)) {
1311
+ dictionary_index_lock_unlock(dict);
1312
+ ret = false;
1313
+ }
1314
+ else {
1315
+ if(hashtable_delete_unsafe(dict, name, name_len, item) == 0)
1316
+ error("DICTIONARY: INTERNAL ERROR: tried to delete item with name '%s' that is not in the index", name);
1317
870
- return freed;
871
-}
1318
+ dictionary_index_lock_unlock(dict);
1319
873
-// ----------------------------------------------------------------------------
874
-// helpers
1320
+ item_free_or_mark_deleted(dict, item);
1321
+ ret = true;
1322
+ }
1323
876
-static NAME_VALUE *dictionary_set_name_value_unsafe(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
877
- if(unlikely(!name)) {
878
- internal_error(true, "DICTIONARY: attempted to dictionary_set() a dictionary item without a name");
1324
+ return ret;
1325
+}
1326
+
1327
+static DICTIONARY_ITEM *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) {
1328
+ if(unlikely(!name || !*name)) {
1329
+ internal_error(
1330
+ true,
1331
+ "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
1332
+ __FUNCTION__,
1333
+ dict->creation_function,
1334
+ dict->creation_line,
1335
+ dict->creation_file);
1336
return NULL;
1337
}
1338
882
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1339
+ if(unlikely(is_dictionary_destroyed(dict))) {
1340
internal_error(true, "DICTIONARY: attempted to dictionary_set() on a destroyed dictionary");
1341
return NULL;
1342
}
1343
887
- internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: inserting dictionary item '%s' without exclusive access to dictionary", name);
888
-
889
- size_t name_len = strlen(name) + 1; // we need the terminating null too
1344
+ if(name_len == -1)
1345
+ name_len = (ssize_t)strlen(name) + 1; // we need the terminating null too
1346
1347
debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
1348
1357
// But the caller has the option to do this on his/her own.
1358
// So, let's do the fastest here and let the caller decide the flow of calls.
1359
904
- NAME_VALUE *nv, **pnv = (NAME_VALUE **)hashtable_insert_unsafe(dict, name, name_len);
905
- if(likely(*pnv == 0)) {
906
- // a new item added to the index
907
- nv = *pnv = namevalue_create_unsafe(dict, name, name_len, value, value_len);
908
- hashtable_inserted_name_value_unsafe(dict, nv);
909
- linkedlist_namevalue_link_unsafe(dict, nv);
910
- nv->flags |= NAME_VALUE_FLAG_NEW_OR_UPDATED;
911
- }
912
- else {
913
- // the item is already in the index
914
- // so, either we will return the old one
915
- // or overwrite the value, depending on dictionary flags
1360
+ dictionary_index_lock_wrlock(dict);
1361
917
- // We should not compare the values here!
918
- // even if they are the same, we have to do the whole job
919
- // so that the callbacks will be called.
1362
+ bool added_or_updated = false;
1363
+ size_t spins = 0;
1364
+ DICTIONARY_ITEM *item = NULL;
1365
+ do {
1366
+ DICTIONARY_ITEM **item_pptr = (DICTIONARY_ITEM **)hashtable_insert_unsafe(dict, name, name_len);
1367
+ if (likely(*item_pptr == 0)) {
1368
+ // a new item added to the index
1369
921
- nv = *pnv;
1370
+ // create the dictionary item
1371
+ item = *item_pptr = item_create_with_hooks(dict, name, name_len, value, value_len, constructor_data, master_item);
1372
923
- if(!(dict->flags & DICTIONARY_FLAG_DONT_OVERWRITE_VALUE)) {
924
- namevalue_reset_unsafe(dict, nv, value, value_len);
925
- nv->flags |= NAME_VALUE_FLAG_NEW_OR_UPDATED;
926
- }
1373
+ // call the hashtable react
1374
+ hashtable_inserted_item_unsafe(dict, item);
1375
928
- else if(dict->conflict_callback) {
929
- dict->conflict_callback(namevalue_get_name(nv), nv->value, value, dict->conflict_callback_data);
930
- nv->flags |= NAME_VALUE_FLAG_NEW_OR_UPDATED;
931
- }
1376
+ // unlock the index lock, before we add it to the linked list
1377
+ // DONT DO IT THE OTHER WAY AROUND - DO NOT CROSS THE LOCKS!
1378
+ dictionary_index_lock_unlock(dict);
1379
1380
+ item_linked_list_add(dict, item);
1381
+ added_or_updated = true;
1382
+ }
1383
else {
934
- // we did really nothing!
935
- // make sure this flag is not set.
936
- nv->flags &= ~NAME_VALUE_FLAG_NEW_OR_UPDATED;
1384
+ if(item_check_and_acquire_advanced(dict, *item_pptr, true) != ITEM_OK) {
1385
+ spins++;
1386
+ continue;
1387
+ }
1388
+
1389
+ // the item is already in the index
1390
+ // so, either we will return the old one
1391
+ // or overwrite the value, depending on dictionary flags
1392
+
1393
+ // We should not compare the values here!
1394
+ // even if they are the same, we have to do the whole job
1395
+ // so that the callbacks will be called.
1396
+
1397
+ item = *item_pptr;
1398
+
1399
+ if(is_view_dictionary(dict)) {
1400
+ // view dictionary
1401
+ // the item is already there and can be used
1402
+ if(item->shared != master_item->shared)
1403
+ 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.");
1404
+ }
1405
+ else {
1406
+ // master dictionary
1407
+ // the user wants to reset its value
1408
+
1409
+ if (!(dict->options & DICT_OPTION_DONT_OVERWRITE_VALUE)) {
1410
+ item_reset_value_with_hooks(dict, item, value, value_len, constructor_data);
1411
+ added_or_updated = true;
1412
+ }
1413
+
1414
+ else if (dictionary_execute_conflict_callback(dict, item, value, constructor_data)) {
1415
+ dictionary_version_increment(dict);
1416
+ added_or_updated = true;
1417
+ }
1418
+
1419
+ else {
1420
+ // we did really nothing!
1421
+ ;
1422
+ }
1423
+ }
1424
+
1425
+ dictionary_index_lock_unlock(dict);
1426
}
938
- }
1427
+ } while(!item);
1428
+
1429
940
- return nv;
1430
+ if(unlikely(spins > 0 && dict->stats))
1431
+ DICTIONARY_STATS_INSERT_SPINS_PLUS(dict, spins);
1432
+
1433
+ if(is_master_dictionary(dict) && added_or_updated)
1434
+ dictionary_execute_react_callback(dict, item, constructor_data);
1435
+
1436
+ return item;
1437
}
1438
943
-static NAME_VALUE *dictionary_get_name_value_unsafe(DICTIONARY *dict, const char *name) {
944
- if(unlikely(!name)) {
945
- internal_error(true, "attempted to dictionary_get() without a name");
1439
+static DICTIONARY_ITEM *item_find_and_acquire(DICTIONARY *dict, const char *name, ssize_t name_len) {
1440
+ if(unlikely(!name || !*name)) {
1441
+ internal_error(
1442
+ true,
1443
+ "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
1444
+ __FUNCTION__,
1445
+ dict->creation_function,
1446
+ dict->creation_line,
1447
+ dict->creation_file);
1448
return NULL;
1449
}
1450
949
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1451
+ if(unlikely(is_dictionary_destroyed(dict))) {
1452
internal_error(true, "DICTIONARY: attempted to dictionary_get() on a destroyed dictionary");
1453
return NULL;
1454
}
1455
954
- size_t name_len = strlen(name) + 1; // we need the terminating null too
1456
+ if(name_len == -1)
1457
+ name_len = (ssize_t)strlen(name) + 1; // we need the terminating null too
1458
1459
debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name);
1460
958
- NAME_VALUE *nv = hashtable_get_unsafe(dict, name, name_len);
959
- if(unlikely(!nv)) {
960
- debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name);
961
- return NULL;
1461
+ dictionary_index_lock_rdlock(dict);
1462
+
1463
+ DICTIONARY_ITEM *item = hashtable_get_unsafe(dict, name, name_len);
1464
+ if(unlikely(item && !item_check_and_acquire(dict, item))) {
1465
+ item = NULL;
1466
+ DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(dict);
1467
}
1468
964
- debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
965
- return nv;
1469
+ dictionary_index_lock_unlock(dict);
1470
+
1471
+ return item;
1472
}
1473
1474
// ----------------------------------------------------------------------------
969
-// API - items management
1475
+// delayed destruction of dictionaries
1476
971
-void *dictionary_set_unsafe(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
972
- NAME_VALUE *nv = dictionary_set_name_value_unsafe(dict, name, value, value_len);
1477
+static bool dictionary_free_all_resources(DICTIONARY *dict, size_t *mem, bool force) {
1478
+ if(mem)
1479
+ *mem = 0;
1480
974
- if(unlikely(dict->react_callback && nv && (nv->flags & NAME_VALUE_FLAG_NEW_OR_UPDATED))) {
975
- // we need to call the react callback with a reference counter on nv
976
- reference_counter_acquire(dict, nv);
977
- dict->react_callback(namevalue_get_name(nv), nv->value, dict->react_callback_data);
978
- reference_counter_release(dict, nv, false);
979
- }
980
-
981
- return nv ? nv->value : NULL;
982
-}
983
-
984
-void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
985
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
986
- NAME_VALUE *nv = dictionary_set_name_value_unsafe(dict, name, value, value_len);
987
-
988
- // we need to get a reference counter for the react callback
989
- // before we unlock the dictionary
990
- if(unlikely(dict->react_callback && nv && (nv->flags & NAME_VALUE_FLAG_NEW_OR_UPDATED)))
991
- reference_counter_acquire(dict, nv);
1481
+ if(!force && dictionary_referenced_items(dict))
1482
+ return false;
1483
993
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
1484
+ size_t dict_size = 0, counted_items = 0, item_size = 0, index_size = 0;
1485
+ (void)counted_items;
1486
995
- if(unlikely(dict->react_callback && nv && (nv->flags & NAME_VALUE_FLAG_NEW_OR_UPDATED))) {
996
- // we got the reference counter we need, above
997
- dict->react_callback(namevalue_get_name(nv), nv->value, dict->react_callback_data);
998
- reference_counter_release(dict, nv, false);
999
- }
1487
+#ifdef NETDATA_INTERNAL_CHECKS
1488
+ long int entries = dict->entries;
1489
+ long int referenced_items = dict->referenced_items;
1490
+ long int pending_deletion_items = dict->pending_deletion_items;
1491
+ const char *creation_function = dict->creation_function;
1492
+ const char *creation_file = dict->creation_file;
1493
+ size_t creation_line = dict->creation_line;
1494
+#endif
1495
1001
- return nv ? nv->value : NULL;
1002
-}
1496
+ // destroy the index
1497
+ index_size += hashtable_destroy_unsafe(dict);
1498
1004
-DICTIONARY_ITEM *dictionary_set_and_acquire_item_unsafe(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
1005
- NAME_VALUE *nv = dictionary_set_name_value_unsafe(dict, name, value, value_len);
1499
+ ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
1500
+ DICTIONARY_ITEM *item = dict->items.list;
1501
+ while (item) {
1502
+ // cache item->next
1503
+ // because we are going to free item
1504
+ DICTIONARY_ITEM *item_next = item->next;
1505
+ item_size += item_free_with_hooks(dict, item);
1506
+ item = item_next;
1507
1007
- if(unlikely(!nv))
1008
- return NULL;
1508
+ DICTIONARY_ENTRIES_MINUS1(dict);
1509
1010
- reference_counter_acquire(dict, nv);
1510
+ // to speed up destruction, we don't
1511
+ // unlink item from the linked-list here
1512
1012
- if(unlikely(dict->react_callback && (nv->flags & NAME_VALUE_FLAG_NEW_OR_UPDATED))) {
1013
- dict->react_callback(namevalue_get_name(nv), nv->value, dict->react_callback_data);
1513
+ counted_items++;
1514
}
1515
+ dict->items.list = NULL;
1516
+ ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1517
1016
- return (DICTIONARY_ITEM *)nv;
1017
-}
1018
-
1019
-DICTIONARY_ITEM *dictionary_set_and_acquire_item(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
1020
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
1021
- NAME_VALUE *nv = dictionary_set_name_value_unsafe(dict, name, value, value_len);
1518
+ dict_size += dictionary_locks_destroy(dict);
1519
+ dict_size += reference_counter_free(dict);
1520
+ dict_size += dictionary_hooks_free(dict);
1521
+ dict_size += sizeof(DICTIONARY);
1522
+ DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(DICTIONARY), 0);
1523
1023
- // we need to get the reference counter before we unlock
1024
- if(nv) reference_counter_acquire(dict, nv);
1524
+ freez(dict);
1525
1026
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
1526
+ internal_error(
1527
+ true,
1528
+ "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).",
1529
+ creation_function,
1530
+ creation_line,
1531
+ creation_file,
1532
+ entries, counted_items, referenced_items, pending_deletion_items,
1533
+ dict_size, sizeof(DICTIONARY), sizeof(DICTIONARY_ITEM) + sizeof(DICTIONARY_ITEM_SHARED));
1534
1028
- if(unlikely(dict->react_callback && nv && (nv->flags & NAME_VALUE_FLAG_NEW_OR_UPDATED))) {
1029
- // we already have a reference counter, for the caller, no need for another one
1030
- dict->react_callback(namevalue_get_name(nv), nv->value, dict->react_callback_data);
1031
- }
1535
+ if(mem)
1536
+ *mem = dict_size + item_size + index_size;
1537
1033
- return (DICTIONARY_ITEM *)nv;
1538
+ return true;
1539
}
1540
1036
-void *dictionary_get_unsafe(DICTIONARY *dict, const char *name) {
1037
- NAME_VALUE *nv = dictionary_get_name_value_unsafe(dict, name);
1038
-
1039
- if(unlikely(!nv))
1040
- return NULL;
1541
+netdata_mutex_t dictionaries_waiting_to_be_destroyed_mutex = NETDATA_MUTEX_INITIALIZER;
1542
+static DICTIONARY *dictionaries_waiting_to_be_destroyed = NULL;
1543
1042
- return nv->value;
1043
-}
1044
-
1045
-void *dictionary_get(DICTIONARY *dict, const char *name) {
1046
- dictionary_lock(dict, DICTIONARY_LOCK_READ);
1047
- void *ret = dictionary_get_unsafe(dict, name);
1048
- dictionary_unlock(dict, DICTIONARY_LOCK_READ);
1049
- return ret;
1050
-}
1544
+void dictionary_queue_for_destruction(DICTIONARY *dict) {
1545
+ if(is_dictionary_destroyed(dict))
1546
+ return;
1547
1052
-DICTIONARY_ITEM *dictionary_get_and_acquire_item_unsafe(DICTIONARY *dict, const char *name) {
1053
- NAME_VALUE *nv = dictionary_get_name_value_unsafe(dict, name);
1548
+ DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
1549
+ dict_flag_set(dict, DICT_FLAG_DESTROYED);
1550
1055
- if(unlikely(!nv))
1056
- return NULL;
1551
+ netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
1552
1058
- reference_counter_acquire(dict, nv);
1059
- return (DICTIONARY_ITEM *)nv;
1060
-}
1553
+ dict->next = dictionaries_waiting_to_be_destroyed;
1554
+ dictionaries_waiting_to_be_destroyed = dict;
1555
1062
-DICTIONARY_ITEM *dictionary_get_and_acquire_item(DICTIONARY *dict, const char *name) {
1063
- dictionary_lock(dict, DICTIONARY_LOCK_READ);
1064
- void *ret = dictionary_get_and_acquire_item_unsafe(dict, name);
1065
- dictionary_unlock(dict, DICTIONARY_LOCK_READ);
1066
- return ret;
1556
+ netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
1557
}
1558
1069
-DICTIONARY_ITEM *dictionary_acquired_item_dup(DICTIONARY_ITEM *item) {
1070
- if(unlikely(!item)) return NULL;
1071
- reference_counter_increase((NAME_VALUE *)item);
1072
- return item;
1073
-}
1074
-
1075
-const char *dictionary_acquired_item_name(DICTIONARY_ITEM *item) {
1076
- if(unlikely(!item)) return NULL;
1077
- return namevalue_get_name((NAME_VALUE *)item);
1078
-}
1559
+void cleanup_destroyed_dictionaries(void) {
1560
+ if(!dictionaries_waiting_to_be_destroyed)
1561
+ return;
1562
1080
-void *dictionary_acquired_item_value(DICTIONARY_ITEM *item) {
1081
- if(unlikely(!item)) return NULL;
1082
- return ((NAME_VALUE *)item)->value;
1083
-}
1563
+ netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
1564
1085
-void dictionary_acquired_item_release_unsafe(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1086
- if(unlikely(!item)) return;
1565
+ DICTIONARY *dict, *last = NULL, *next = NULL;
1566
+ for(dict = dictionaries_waiting_to_be_destroyed; dict ; dict = next) {
1567
+ next = dict->next;
1568
1569
#ifdef NETDATA_INTERNAL_CHECKS
1089
- if(((NAME_VALUE *)item)->dict != dict)
1090
- fatal("DICTIONARY: %s(): name_value item with name '%s' does not belong to this dictionary", __FUNCTION__, namevalue_get_name((NAME_VALUE *)item));
1570
+ size_t line = dict->creation_line;
1571
+ const char *file = dict->creation_file;
1572
+ const char *function = dict->creation_function;
1573
#endif
1574
1093
- reference_counter_release(dict, (NAME_VALUE *)item, false);
1094
-}
1095
-
1096
-void dictionary_acquired_item_release(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1097
- if(unlikely(!item)) return;
1575
+ DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(dict);
1576
+ if(dictionary_free_all_resources(dict, NULL, false)) {
1577
1099
-#ifdef NETDATA_INTERNAL_CHECKS
1100
- if(((NAME_VALUE *)item)->dict != dict)
1101
- fatal("DICTIONARY: %s(): name_value item with name '%s' does not belong to this dictionary", __FUNCTION__, namevalue_get_name((NAME_VALUE *)item));
1102
-#endif
1103
-
1104
- // no need to get a lock here
1105
- // we pass the last parameter to reference_counter_release() as true
1106
- // so that the release may get a write-lock if required to clean up
1578
+ internal_error(
1579
+ true,
1580
+ "DICTIONARY: freed dictionary with delayed destruction, created from %s() %zu@%s.",
1581
+ function, line, file);
1582
1108
- reference_counter_release(dict, (NAME_VALUE *)item, true);
1583
+ if(last) last->next = next;
1584
+ else dictionaries_waiting_to_be_destroyed = next;
1585
+ }
1586
+ else {
1587
+ DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
1588
+ last = dict;
1589
+ }
1590
+ }
1591
1110
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED))
1111
- dictionary_destroy(dict);
1592
+ netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
1593
}
1594
1114
-int dictionary_del_unsafe(DICTIONARY *dict, const char *name) {
1115
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1116
- internal_error(true, "DICTIONARY: attempted to dictionary_del() on a destroyed dictionary");
1117
- return -1;
1118
- }
1595
+// ----------------------------------------------------------------------------
1596
+// API internal checks
1597
1120
- if(unlikely(!name || !*name)) {
1121
- internal_error(true, "DICTIONARY: attempted to dictionary_del() without a name");
1122
- return -1;
1598
+#ifdef NETDATA_INTERNAL_CHECKS
1599
+#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)
1600
+static inline void api_internal_check_with_trace(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *function, bool allow_null_dict, bool allow_null_item) {
1601
+ if(!allow_null_dict && !dict) {
1602
+ internal_error(
1603
+ item,
1604
+ "DICTIONARY: attempted to %s() with a NULL dictionary, passing an item created from %s() %zu@%s.",
1605
+ function,
1606
+ item->dict->creation_function,
1607
+ item->dict->creation_line,
1608
+ item->dict->creation_file);
1609
+ fatal("DICTIONARY: attempted to %s() but item is NULL", function);
1610
}
1611
1125
- internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: INTERNAL ERROR: deleting dictionary item '%s' without exclusive access to dictionary", name);
1126
-
1127
- size_t name_len = strlen(name) + 1; // we need the terminating null too
1128
-
1129
- debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
1130
-
1131
- // Unfortunately, the JudyHSDel() does not return the value of the
1132
- // item that was deleted, so we have to find it before we delete it,
1133
- // since we need to release our structures too.
1134
-
1135
- int ret;
1136
- NAME_VALUE *nv = hashtable_get_unsafe(dict, name, name_len);
1137
- if(unlikely(!nv)) {
1138
- debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name);
1139
- ret = -1;
1612
+ if(!allow_null_item && !item) {
1613
+ internal_error(
1614
+ true,
1615
+ "DICTIONARY: attempted to %s() without an item on a dictionary created from %s() %zu@%s.",
1616
+ function,
1617
+ dict?dict->creation_function:"unknown",
1618
+ dict?dict->creation_line:0,
1619
+ dict?dict->creation_file:"unknown");
1620
+ fatal("DICTIONARY: attempted to %s() but item is NULL", function);
1621
}
1141
- else {
1142
- debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
1622
1144
- if(hashtable_delete_unsafe(dict, name, name_len, nv) == 0)
1145
- error("DICTIONARY: INTERNAL ERROR: tried to delete item with name '%s' that is not in the index", name);
1623
+ if(dict && item && dict != item->dict) {
1624
+ internal_error(
1625
+ true,
1626
+ "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.",
1627
+ function,
1628
+ dict->creation_function,
1629
+ dict->creation_line,
1630
+ dict->creation_file,
1631
+ item->dict->creation_function,
1632
+ item->dict->creation_line,
1633
+ item->dict->creation_file
1634
+ );
1635
+ fatal("DICTIONARY: %s(): item does not belong to this dictionary.", function);
1636
+ }
1637
1147
- if(name_value_can_be_deleted(dict, nv)) {
1148
- linkedlist_namevalue_unlink_unsafe(dict, nv);
1149
- namevalue_destroy_unsafe(dict, nv);
1638
+ if(item) {
1639
+ REFCOUNT refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
1640
+ if (unlikely(refcount <= 0)) {
1641
+ internal_error(
1642
+ true,
1643
+ "DICTIONARY: attempted to %s() of an item with reference counter = %d on a dictionary created from %s() %zu@%s",
1644
+ function,
1645
+ refcount,
1646
+ item->dict->creation_function,
1647
+ item->dict->creation_line,
1648
+ item->dict->creation_file);
1649
+ fatal("DICTIONARY: attempted to %s but item is having refcount = %d", function, refcount);
1650
}
1151
- else
1152
- nv->flags |= NAME_VALUE_FLAG_DELETED;
1153
-
1154
- ret = 0;
1155
-
1156
- DICTIONARY_STATS_ENTRIES_MINUS1(dict);
1157
-
1651
}
1159
- return ret;
1160
-}
1161
-
1162
-int dictionary_del(DICTIONARY *dict, const char *name) {
1163
- dictionary_lock(dict, DICTIONARY_LOCK_WRITE);
1164
- int ret = dictionary_del_unsafe(dict, name);
1165
- dictionary_unlock(dict, DICTIONARY_LOCK_WRITE);
1166
- return ret;
1652
}
1653
+#else
1654
+#define api_internal_check(dict, item, allow_null_dict, allow_null_item) debug_dummy()
1655
+#endif
1656
1169
-// ----------------------------------------------------------------------------
1170
-// traversal with loop
1171
-
1172
-void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) {
1173
- if(unlikely(!dfe || !dict)) return NULL;
1174
-
1175
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1176
- internal_error(true, "DICTIONARY: attempted to dictionary_foreach_start_rw() on a destroyed dictionary");
1177
- dfe->last_item = NULL;
1178
- dfe->name = NULL;
1179
- dfe->value = NULL;
1180
- return NULL;
1657
+#define api_is_name_good(dict, name, name_len) api_is_name_good_with_trace(dict, name, name_len, __FUNCTION__)
1658
+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) {
1659
+ if(unlikely(!name)) {
1660
+ internal_error(
1661
+ true,
1662
+ "DICTIONARY: attempted to %s() with name = NULL on a dictionary created from %s() %zu@%s.",
1663
+ function,
1664
+ dict?dict->creation_function:"unknown",
1665
+ dict?dict->creation_line:0,
1666
+ dict?dict->creation_file:"unknown");
1667
+ return false;
1668
}
1669
1183
- dfe->dict = dict;
1184
- dfe->rw = rw;
1185
- dfe->started_ut = now_realtime_usec();
1186
-
1187
- dictionary_lock(dict, dfe->rw);
1188
-
1189
- DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1190
-
1191
- // get the first item from the list
1192
- NAME_VALUE *nv = dict->first_item;
1193
-
1194
- // skip all the deleted items
1195
- while(nv && (nv->flags & NAME_VALUE_FLAG_DELETED))
1196
- nv = nv->next;
1197
-
1198
- if(likely(nv)) {
1199
- dfe->last_item = nv;
1200
- dfe->name = (char *)namevalue_get_name(nv);
1201
- dfe->value = nv->value;
1202
- reference_counter_acquire(dict, nv);
1203
- }
1204
- else {
1205
- dfe->last_item = NULL;
1206
- dfe->name = NULL;
1207
- dfe->value = NULL;
1670
+ if(unlikely(!*name)) {
1671
+ internal_error(
1672
+ true,
1673
+ "DICTIONARY: attempted to %s() with empty name on a dictionary created from %s() %zu@%s.",
1674
+ function,
1675
+ dict?dict->creation_function:"unknown",
1676
+ dict?dict->creation_line:0,
1677
+ dict?dict->creation_file:"unknown");
1678
+ return false;
1679
}
1680
1210
- if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT))
1211
- dictionary_unlock(dfe->dict, dfe->rw);
1681
+ internal_error(
1682
+ name_len > 0 && name_len != (ssize_t)(strlen(name) + 1),
1683
+ "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu (incl. '\\0'), but the supplied name_len = %ld, on a dictionary created from %s() %zu@%s.",
1684
+ function,
1685
+ name,
1686
+ strlen(name) + 1,
1687
+ name_len,
1688
+ dict?dict->creation_function:"unknown",
1689
+ dict?dict->creation_line:0,
1690
+ dict?dict->creation_file:"unknown");
1691
+
1692
+ internal_error(
1693
+ name_len <= 0 && name_len != -1,
1694
+ "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu (incl. '\\0'), but the supplied name_len = %ld, on a dictionary created from %s() %zu@%s.",
1695
+ function,
1696
+ name,
1697
+ strlen(name) + 1,
1698
+ name_len,
1699
+ dict?dict->creation_function:"unknown",
1700
+ dict?dict->creation_line:0,
1701
+ dict?dict->creation_file:"unknown");
1702
1213
- return dfe->value;
1703
+ return true;
1704
}
1705
1216
-void *dictionary_foreach_next(DICTFE *dfe) {
1217
- if(unlikely(!dfe || !dfe->dict)) return NULL;
1706
+// ----------------------------------------------------------------------------
1707
+// API - dictionary management
1708
1219
- if(unlikely(dfe->dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1220
- internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
1221
- dfe->last_item = NULL;
1222
- dfe->name = NULL;
1223
- dfe->value = NULL;
1224
- return NULL;
1225
- }
1709
+static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dictionary_stats *stats) {
1710
+ cleanup_destroyed_dictionaries();
1711
1227
- if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT))
1228
- dictionary_lock(dfe->dict, dfe->rw);
1712
+ DICTIONARY *dict = callocz(1, sizeof(DICTIONARY));
1713
+ dict->options = options;
1714
+ dict->stats = stats;
1715
1230
- // the item we just did
1231
- NAME_VALUE *nv = (NAME_VALUE *)dfe->last_item;
1716
+ size_t dict_size = 0;
1717
+ dict_size += sizeof(DICTIONARY);
1718
+ dict_size += dictionary_locks_init(dict);
1719
+ dict_size += reference_counter_init(dict);
1720
+ dict_size += hashtable_init_unsafe(dict);
1721
1233
- // get the next item from the list
1234
- NAME_VALUE *nv_next = (nv) ? nv->next : NULL;
1722
+ DICTIONARY_STATS_PLUS_MEMORY(dict, 0, dict_size, 0);
1723
1236
- // skip all the deleted items
1237
- while(nv_next && (nv_next->flags & NAME_VALUE_FLAG_DELETED))
1238
- nv_next = nv_next->next;
1724
+ return dict;
1725
+}
1726
1240
- // release the old, so that it can possibly be deleted
1241
- if(likely(nv))
1242
- reference_counter_release(dfe->dict, nv, false);
1727
+#ifdef NETDATA_INTERNAL_CHECKS
1728
+DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, const char *function, size_t line, const char *file) {
1729
+#else
1730
+DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats) {
1731
+#endif
1732
1244
- if(likely(nv = nv_next)) {
1245
- dfe->last_item = nv;
1246
- dfe->name = (char *)namevalue_get_name(nv);
1247
- dfe->value = nv->value;
1248
- reference_counter_acquire(dfe->dict, nv);
1249
- }
1250
- else {
1251
- dfe->last_item = NULL;
1252
- dfe->name = NULL;
1253
- dfe->value = NULL;
1254
- }
1733
+ DICTIONARY *dict = dictionary_create_internal(options, stats?stats:&dictionary_stats_category_other);
1734
1256
- if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT))
1257
- dictionary_unlock(dfe->dict, dfe->rw);
1735
+#ifdef NETDATA_INTERNAL_CHECKS
1736
+ dict->creation_function = function;
1737
+ dict->creation_file = file;
1738
+ dict->creation_line = line;
1739
+#endif
1740
1259
- return dfe->value;
1741
+ DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
1742
+ return dict;
1743
}
1744
1262
-usec_t dictionary_foreach_done(DICTFE *dfe) {
1263
- if(unlikely(!dfe || !dfe->dict)) return 0;
1264
-
1265
- if(unlikely(dfe->dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1266
- internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
1267
- return 0;
1268
- }
1745
+#ifdef NETDATA_INTERNAL_CHECKS
1746
+DICTIONARY *dictionary_create_view_with_trace(DICTIONARY *master, const char *function, size_t line, const char *file) {
1747
+#else
1748
+DICTIONARY *dictionary_create_view(DICTIONARY *master) {
1749
+#endif
1750
1270
- // the item we just did
1271
- NAME_VALUE *nv = (NAME_VALUE *)dfe->last_item;
1751
+ DICTIONARY *dict = dictionary_create_internal(master->options, master->stats);
1752
+ dict->master = master;
1753
1273
- // release it, so that it can possibly be deleted
1274
- if(likely(nv))
1275
- reference_counter_release(dfe->dict, nv, false);
1754
+ dictionary_hooks_allocate(master);
1755
1277
- if(likely(dfe->rw != DICTIONARY_LOCK_REENTRANT))
1278
- dictionary_unlock(dfe->dict, dfe->rw);
1756
+ if(unlikely(__atomic_load_n(&master->hooks->links, __ATOMIC_SEQ_CST)) < 1)
1757
+ fatal("DICTIONARY: attempted to create a view that has %d links", master->hooks->links);
1758
1280
- dfe->dict = NULL;
1281
- dfe->last_item = NULL;
1282
- dfe->name = NULL;
1283
- dfe->value = NULL;
1759
+ dict->hooks = master->hooks;
1760
+ __atomic_add_fetch(&master->hooks->links, 1, __ATOMIC_SEQ_CST);
1761
1285
- usec_t usec = now_realtime_usec() - dfe->started_ut;
1286
- dfe->started_ut = 0;
1762
+#ifdef NETDATA_INTERNAL_CHECKS
1763
+ dict->creation_function = function;
1764
+ dict->creation_file = file;
1765
+ dict->creation_line = line;
1766
+#endif
1767
1288
- return usec;
1768
+ DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
1769
+ return dict;
1770
}
1771
1291
-// ----------------------------------------------------------------------------
1292
-// API - walk through the dictionary
1293
-// the dictionary is locked for reading while this happens
1294
-// do not use other dictionary calls while walking the dictionary - deadlock!
1772
+void dictionary_flush(DICTIONARY *dict) {
1773
+ if(unlikely(!dict))
1774
+ return;
1775
1296
-int dictionary_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const char *name, void *entry, void *data), void *data) {
1297
- if(unlikely(!dict)) return 0;
1776
+ // delete the index
1777
+ dictionary_index_lock_wrlock(dict);
1778
+ hashtable_destroy_unsafe(dict);
1779
+ dictionary_index_lock_unlock(dict);
1780
1299
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1300
- internal_error(true, "DICTIONARY: attempted to dictionary_walkthrough_rw() on a destroyed dictionary");
1301
- return 0;
1302
- }
1781
+ // delete all items
1782
+ ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE); // get write lock here, to speed it up (it is recursive)
1783
+ DICTIONARY_ITEM *item, *item_next;
1784
+ for (item = dict->items.list; item; item = item_next) {
1785
+ item_next = item->next;
1786
1304
- dictionary_lock(dict, rw);
1787
+ if(!item_flag_check(item, ITEM_FLAG_DELETED))
1788
+ item_free_or_mark_deleted(dict, item);
1789
+ }
1790
+ ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1791
1306
- DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1792
+ DICTIONARY_STATS_DICT_FLUSHES_PLUS1(dict);
1793
+}
1794
1308
- // written in such a way, that the callback can delete the active element
1795
+size_t dictionary_destroy(DICTIONARY *dict) {
1796
+ cleanup_destroyed_dictionaries();
1797
1310
- int ret = 0;
1311
- NAME_VALUE *nv = dict->first_item, *nv_next;
1312
- while(nv) {
1798
+ if(!dict) return 0;
1799
1314
- // skip the deleted items
1315
- if(unlikely(nv->flags & NAME_VALUE_FLAG_DELETED)) {
1316
- nv = nv->next;
1317
- continue;
1318
- }
1800
+ DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(dict);
1801
1320
- // get a reference counter, so that our item will not be deleted
1321
- // while we are using it
1322
- reference_counter_acquire(dict, nv);
1802
+ size_t referenced_items = dictionary_referenced_items(dict);
1803
+ if(referenced_items) {
1804
+ dictionary_flush(dict);
1805
+ dictionary_queue_for_destruction(dict);
1806
1324
- if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
1325
- dictionary_unlock(dict, rw);
1807
+ internal_error(
1808
+ true,
1809
+ "DICTIONARY: delaying destruction of dictionary created from %s() %zu@%s, because it has %ld referenced items in it (%ld total).",
1810
+ dict->creation_function,
1811
+ dict->creation_line,
1812
+ dict->creation_file,
1813
+ dict->referenced_items,
1814
+ dict->entries);
1815
1327
- int r = callback(namevalue_get_name(nv), nv->value, data);
1816
+ return 0;
1817
+ }
1818
1329
- if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
1330
- dictionary_lock(dict, rw);
1819
+ size_t freed;
1820
+ dictionary_free_all_resources(dict, &freed, true);
1821
1332
- // since we have a reference counter, this item cannot be deleted
1333
- // until we release the reference counter, so the pointers are there
1334
- nv_next = nv->next;
1335
- reference_counter_release(dict, nv, false);
1822
+ return freed;
1823
+}
1824
1337
- if(unlikely(r < 0)) {
1338
- ret = r;
1339
- break;
1340
- }
1825
+// ----------------------------------------------------------------------------
1826
+// SET an item to the dictionary
1827
1342
- ret += r;
1828
+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) {
1829
+ if(unlikely(!api_is_name_good(dict, name, name_len)))
1830
+ return NULL;
1831
1344
- nv = nv_next;
1345
- }
1832
+ api_internal_check(dict, NULL, false, true);
1833
1347
- dictionary_unlock(dict, rw);
1834
+ if(unlikely(is_view_dictionary(dict)))
1835
+ fatal("DICTIONARY: this dictionary is a view, you cannot add items other than the ones from the master dictionary.");
1836
1349
- return ret;
1837
+ DICTIONARY_ITEM *item = item_add_or_reset_value_and_acquire(dict, name, name_len, value, value_len, constructor_data, NULL);
1838
+ api_internal_check(dict, item, false, false);
1839
+ return item;
1840
}
1841
1352
-// ----------------------------------------------------------------------------
1353
-// sorted walkthrough
1842
+void *dictionary_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data) {
1843
+ DICTIONARY_ITEM *item = dictionary_set_and_acquire_item_advanced(dict, name, name_len, value, value_len, constructor_data);
1844
1355
-static int dictionary_sort_compar(const void *nv1, const void *nv2) {
1356
- return strcmp(namevalue_get_name((*(NAME_VALUE **)nv1)), namevalue_get_name((*(NAME_VALUE **)nv2)));
1357
-}
1845
+ if(likely(item)) {
1846
+ void *v = item->shared->value;
1847
+ item_release(dict, item);
1848
+ return v;
1849
+ }
1850
1359
-int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const char *name, void *entry, void *data), void *data) {
1360
- if(unlikely(!dict || !dict->entries)) return 0;
1851
+ return NULL;
1852
+}
1853
1362
- if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1363
- internal_error(true, "DICTIONARY: attempted to dictionary_sorted_walkthrough_rw() on a destroyed dictionary");
1364
- return 0;
1365
- }
1854
+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) {
1855
+ if(unlikely(!api_is_name_good(dict, name, name_len)))
1856
+ return NULL;
1857
1367
- dictionary_lock(dict, rw);
1368
- dictionary_defer_all_deletions_unsafe(dict, rw);
1858
+ api_internal_check(dict, NULL, false, true);
1859
1370
- DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1860
+ if(unlikely(is_master_dictionary(dict)))
1861
+ fatal("DICTIONARY: this dictionary is a master, you cannot add items from other dictionaries.");
1862
1372
- size_t count = dict->entries;
1373
- NAME_VALUE **array = mallocz(sizeof(NAME_VALUE *) * count);
1863
+ dictionary_acquired_item_dup(dict->master, master_item);
1864
+ DICTIONARY_ITEM *item = item_add_or_reset_value_and_acquire(dict, name, name_len, NULL, 0, NULL, master_item);
1865
+ dictionary_acquired_item_release(dict->master, master_item);
1866
1375
- size_t i;
1376
- NAME_VALUE *nv;
1377
- for(nv = dict->first_item, i = 0; nv && i < count ;nv = nv->next) {
1378
- if(likely(!(nv->flags & NAME_VALUE_FLAG_DELETED)))
1379
- array[i++] = nv;
1380
- }
1867
+ api_internal_check(dict, item, false, false);
1868
+ return item;
1869
+}
1870
1382
- internal_error(nv != NULL, "DICTIONARY: during sorting expected to have %zu items in dictionary, but there are more. Sorted results may be incomplete. Dictionary fails to maintain an accurate number of the number of entries it has.", count);
1871
+void *dictionary_view_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, DICTIONARY_ITEM *master_item) {
1872
+ DICTIONARY_ITEM *item = dictionary_view_set_and_acquire_item_advanced(dict, name, name_len, master_item);
1873
1384
- if(unlikely(i != count)) {
1385
- internal_error(true, "DICTIONARY: during sorting expected to have %zu items in dictionary, but there are %zu. Sorted results may be incomplete. Dictionary fails to maintain an accurate number of the number of entries it has.", count, i);
1386
- count = i;
1874
+ if(likely(item)) {
1875
+ void *v = item->shared->value;
1876
+ item_release(dict, item);
1877
+ return v;
1878
}
1879
1389
- qsort(array, count, sizeof(NAME_VALUE *), dictionary_sort_compar);
1880
+ return NULL;
1881
+}
1882
1391
- int ret = 0;
1392
- for(i = 0; i < count ;i++) {
1393
- nv = array[i];
1394
- if(likely(!(nv->flags & NAME_VALUE_FLAG_DELETED))) {
1395
- reference_counter_acquire(dict, nv);
1883
+// ----------------------------------------------------------------------------
1884
+// GET an item from the dictionary
1885
1397
- if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
1398
- dictionary_unlock(dict, rw);
1886
+DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_get_and_acquire_item_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
1887
+ if(unlikely(!api_is_name_good(dict, name, name_len)))
1888
+ return NULL;
1889
1400
- int r = callback(namevalue_get_name(nv), nv->value, data);
1890
+ api_internal_check(dict, NULL, false, true);
1891
+ DICTIONARY_ITEM *item = item_find_and_acquire(dict, name, name_len);
1892
+ api_internal_check(dict, item, false, true);
1893
+ return item;
1894
+}
1895
1402
- if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
1403
- dictionary_lock(dict, rw);
1896
+void *dictionary_get_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
1897
+ DICTIONARY_ITEM *item = dictionary_get_and_acquire_item_advanced(dict, name, name_len);
1898
1405
- reference_counter_release(dict, nv, false);
1406
- if (r < 0) {
1407
- ret = r;
1408
- break;
1409
- }
1410
- ret += r;
1411
- }
1899
+ if(likely(item)) {
1900
+ void *v = item->shared->value;
1901
+ item_release(dict, item);
1902
+ return v;
1903
}
1904
1414
- dictionary_restore_all_deletions_unsafe(dict, rw);
1415
- dictionary_unlock(dict, rw);
1416
- freez(array);
1417
-
1418
- return ret;
1905
+ return NULL;
1906
}
1907
1908
// ----------------------------------------------------------------------------
1422
-// STRING implementation - dedup all STRINGs
1909
+// DUP/REL an item (increase/decrease its reference counter)
1910
1424
-struct netdata_string {
1425
- uint32_t length; // the string length including the terminating '\0'
1911
+DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_acquired_item_dup(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
1912
+ // we allow the item to be NULL here
1913
+ api_internal_check(dict, item, false, true);
1914
1427
- int32_t refcount; // how many times this string is used
1428
- // We use a signed number to be able to detect duplicate frees of a string.
1429
- // If at any point this goes below zero, we have a duplicate free.
1915
+ if(likely(item)) {
1916
+ item_acquire(dict, item);
1917
+ api_internal_check(dict, item, false, false);
1918
+ }
1919
1431
- const char str[]; // the string itself, is appended to this structure
1432
-};
1920
+ return item;
1921
+}
1922
1434
-static struct string_hashtable {
1435
- Pvoid_t JudyHSArray; // the Judy array - hashtable
1436
- netdata_rwlock_t rwlock; // the R/W lock to protect the Judy array
1923
+void dictionary_acquired_item_release(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
1924
+ // we allow the item to be NULL here
1925
+ api_internal_check(dict, item, false, true);
1926
1438
- long int entries; // the number of entries in the index
1439
- long int active_references; // the number of active references alive
1440
- long int memory; // the memory used, without the JudyHS index
1927
+ // no need to get a lock here
1928
+ // we pass the last parameter to reference_counter_release() as true
1929
+ // so that the release may get a write-lock if required to clean up
1930
1442
- size_t inserts; // the number of successful inserts to the index
1443
- size_t deletes; // the number of successful deleted from the index
1444
- size_t searches; // the number of successful searches in the index
1445
- size_t duplications; // when a string is referenced
1446
- size_t releases; // when a string is unreferenced
1931
+ if(likely(item))
1932
+ item_release(dict, item);
1933
+}
1934
1448
-#ifdef NETDATA_INTERNAL_CHECKS
1449
- // internal statistics
1450
- size_t found_deleted_on_search;
1451
- size_t found_available_on_search;
1452
- size_t found_deleted_on_insert;
1453
- size_t found_available_on_insert;
1454
- size_t spins;
1455
-#endif
1935
+// ----------------------------------------------------------------------------
1936
+// get the name/value of an item
1937
1457
-} string_base = {
1458
- .JudyHSArray = NULL,
1459
- .rwlock = NETDATA_RWLOCK_INITIALIZER,
1460
-};
1938
+const char *dictionary_acquired_item_name(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
1939
+ api_internal_check(NULL, item, true, false);
1940
+ return item_get_name(item);
1941
+}
1942
1462
-#ifdef NETDATA_INTERNAL_CHECKS
1463
-#define string_internal_stats_add(var, val) __atomic_add_fetch(&string_base.var, val, __ATOMIC_RELAXED)
1464
-#else
1465
-#define string_internal_stats_add(var, val) do {;} while(0)
1466
-#endif
1943
+void *dictionary_acquired_item_value(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
1944
+ // we allow the item to be NULL here
1945
+ api_internal_check(NULL, item, true, true);
1946
1468
-#define string_stats_atomic_increment(var) __atomic_add_fetch(&string_base.var, 1, __ATOMIC_RELAXED)
1469
-#define string_stats_atomic_decrement(var) __atomic_sub_fetch(&string_base.var, 1, __ATOMIC_RELAXED)
1947
+ if(likely(item))
1948
+ return item->shared->value;
1949
1471
-void string_statistics(size_t *inserts, size_t *deletes, size_t *searches, size_t *entries, size_t *references, size_t *memory, size_t *duplications, size_t *releases) {
1472
- *inserts = string_base.inserts;
1473
- *deletes = string_base.deletes;
1474
- *searches = string_base.searches;
1475
- *entries = (size_t)string_base.entries;
1476
- *references = (size_t)string_base.active_references;
1477
- *memory = (size_t)string_base.memory;
1478
- *duplications = string_base.duplications;
1479
- *releases = string_base.releases;
1950
+ return NULL;
1951
}
1952
1482
-#define string_entry_acquire(se) __atomic_add_fetch(&((se)->refcount), 1, __ATOMIC_SEQ_CST);
1483
-#define string_entry_release(se) __atomic_sub_fetch(&((se)->refcount), 1, __ATOMIC_SEQ_CST);
1953
+// ----------------------------------------------------------------------------
1954
+// DEL an item
1955
1485
-static inline bool string_entry_check_and_acquire(STRING *se) {
1486
- int32_t expected, desired, count = 0;
1487
- do {
1488
- count++;
1956
+bool dictionary_del_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
1957
+ if(unlikely(!api_is_name_good(dict, name, name_len)))
1958
+ return false;
1959
1490
- expected = se->refcount;
1960
+ api_internal_check(dict, NULL, false, true);
1961
+ return item_del(dict, name, name_len);
1962
+}
1963
1492
- if(expected <= 0) {
1493
- // We cannot use this.
1494
- // The reference counter reached value zero,
1495
- // so another thread is deleting this.
1496
- string_internal_stats_add(spins, count - 1);
1497
- return false;
1498
- }
1964
+// ----------------------------------------------------------------------------
1965
+// traversal with loop
1966
+
1967
+void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) {
1968
+ if(unlikely(!dfe || !dict)) return NULL;
1969
1500
- desired = expected + 1;
1970
+ if(unlikely(is_dictionary_destroyed(dict))) {
1971
+ internal_error(true, "DICTIONARY: attempted to dictionary_foreach_start_rw() on a destroyed dictionary");
1972
+ dfe->counter = 0;
1973
+ dfe->item = NULL;
1974
+ dfe->name = NULL;
1975
+ dfe->value = NULL;
1976
+ return NULL;
1977
}
1502
- while(!__atomic_compare_exchange_n(&se->refcount, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));
1978
1504
- string_internal_stats_add(spins, count - 1);
1979
+ dfe->counter = 0;
1980
+ dfe->dict = dict;
1981
+ dfe->rw = rw;
1982
1506
- // statistics
1507
- // string_base.active_references is altered at the in string_strdupz() and string_freez()
1508
- string_stats_atomic_increment(duplications);
1983
+ ll_recursive_lock(dict, dfe->rw);
1984
1510
- return true;
1511
-}
1985
+ DICTIONARY_STATS_TRAVERSALS_PLUS1(dict);
1986
1513
-STRING *string_dup(STRING *string) {
1514
- if(unlikely(!string)) return NULL;
1987
+ // get the first item from the list
1988
+ DICTIONARY_ITEM *item = dict->items.list;
1989
1516
-#ifdef NETDATA_INTERNAL_CHECKS
1517
- if(unlikely(__atomic_load_n(&string->refcount, __ATOMIC_SEQ_CST) <= 0))
1518
- fatal("STRING: tried to %s() a string that is freed (it has %d references).", __FUNCTION__, string->refcount);
1519
-#endif
1990
+ // skip all the deleted items
1991
+ while(item && !item_check_and_acquire(dict, item))
1992
+ item = item->next;
1993
1521
- string_entry_acquire(string);
1994
+ if(likely(item)) {
1995
+ dfe->item = item;
1996
+ dfe->name = (char *)item_get_name(item);
1997
+ dfe->value = item->shared->value;
1998
+ }
1999
+ else {
2000
+ dfe->item = NULL;
2001
+ dfe->name = NULL;
2002
+ dfe->value = NULL;
2003
+ }
2004
1523
- // statistics
1524
- string_stats_atomic_increment(active_references);
1525
- string_stats_atomic_increment(duplications);
2005
+ if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT))
2006
+ ll_recursive_unlock(dfe->dict, dfe->rw);
2007
1527
- return string;
2008
+ return dfe->value;
2009
}
2010
1530
-// Search the index and return an ACQUIRED string entry, or NULL
1531
-static inline STRING *string_index_search(const char *str, size_t length) {
1532
- if(unlikely(!string_base.JudyHSArray))
2011
+void *dictionary_foreach_next(DICTFE *dfe) {
2012
+ if(unlikely(!dfe || !dfe->dict)) return NULL;
2013
+
2014
+ if(unlikely(is_dictionary_destroyed(dfe->dict))) {
2015
+ internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
2016
+ dfe->item = NULL;
2017
+ dfe->name = NULL;
2018
+ dfe->value = NULL;
2019
return NULL;
2020
+ }
2021
1535
- STRING *string;
2022
+ if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT))
2023
+ ll_recursive_lock(dfe->dict, dfe->rw);
2024
1537
- // Find the string in the index
1538
- // With a read-lock so that multiple readers can use the index concurrently.
2025
+ // the item we just did
2026
+ DICTIONARY_ITEM *item = dfe->item;
2027
1540
- netdata_rwlock_rdlock(&string_base.rwlock);
2028
+ // get the next item from the list
2029
+ DICTIONARY_ITEM *item_next = (item) ? item->next : NULL;
2030
1542
- Pvoid_t *Rc;
1543
- Rc = JudyHSGet(string_base.JudyHSArray, (void *)str, length);
1544
- if(likely(Rc)) {
1545
- // found in the hash table
1546
- string = *Rc;
2031
+ // skip all the deleted items until one that can be acquired is found
2032
+ while(item_next && !item_check_and_acquire(dfe->dict, item_next))
2033
+ item_next = item_next->next;
2034
1548
- if(string_entry_check_and_acquire(string)) {
1549
- // we can use this entry
1550
- string_internal_stats_add(found_available_on_search, 1);
1551
- }
1552
- else {
1553
- // this entry is about to be deleted by another thread
1554
- // do not touch it, let it go...
1555
- string = NULL;
1556
- string_internal_stats_add(found_deleted_on_search, 1);
1557
- }
2035
+ if(likely(item)) {
2036
+ item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dfe->dict, item, dfe->rw);
2037
+ // item_release(dfe->dict, item);
2038
+ }
2039
+
2040
+ item = item_next;
2041
+ if(likely(item)) {
2042
+ dfe->item = item;
2043
+ dfe->name = (char *)item_get_name(item);
2044
+ dfe->value = item->shared->value;
2045
+ dfe->counter++;
2046
}
2047
else {
1560
- // not found in the hash table
1561
- string = NULL;
2048
+ dfe->item = NULL;
2049
+ dfe->name = NULL;
2050
+ dfe->value = NULL;
2051
}
2052
1564
- string_stats_atomic_increment(searches);
1565
- netdata_rwlock_unlock(&string_base.rwlock);
2053
+ if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT))
2054
+ ll_recursive_unlock(dfe->dict, dfe->rw);
2055
1567
- return string;
2056
+ return dfe->value;
2057
}
2058
1570
-// Insert a string to the index and return an ACQUIRED string entry,
1571
-// or NULL if the call needs to be retried (a deleted entry with the same key is still in the index)
1572
-// The returned entry is ACQUIRED and it can either be:
1573
-// 1. a new item inserted, or
1574
-// 2. an item found in the index that is not currently deleted
1575
-static inline STRING *string_index_insert(const char *str, size_t length) {
1576
- STRING *string;
1577
-
1578
- netdata_rwlock_wrlock(&string_base.rwlock);
1579
-
1580
- STRING **ptr;
1581
- {
1582
- JError_t J_Error;
1583
- Pvoid_t *Rc = JudyHSIns(&string_base.JudyHSArray, (void *)str, length, &J_Error);
1584
- if (unlikely(Rc == PJERR)) {
1585
- fatal(
1586
- "STRING: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
1587
- str,
1588
- JU_ERRNO(&J_Error),
1589
- JU_ERRID(&J_Error));
1590
- }
1591
- ptr = (STRING **)Rc;
1592
- }
2059
+void dictionary_foreach_done(DICTFE *dfe) {
2060
+ if(unlikely(!dfe || !dfe->dict)) return;
2061
1594
- if (likely(*ptr == 0)) {
1595
- // a new item added to the index
1596
- size_t mem_size = sizeof(STRING) + length;
1597
- string = mallocz(mem_size);
1598
- strcpy((char *)string->str, str);
1599
- string->length = length;
1600
- string->refcount = 1;
1601
- *ptr = string;
1602
- string_base.inserts++;
1603
- string_base.entries++;
1604
- string_base.memory += (long)mem_size;
2062
+ if(unlikely(is_dictionary_destroyed(dfe->dict))) {
2063
+ internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
2064
+ return;
2065
}
1606
- else {
1607
- // the item is already in the index
1608
- string = *ptr;
2066
1610
- if(string_entry_check_and_acquire(string)) {
1611
- // we can use this entry
1612
- string_internal_stats_add(found_available_on_insert, 1);
1613
- }
1614
- else {
1615
- // this entry is about to be deleted by another thread
1616
- // do not touch it, let it go...
1617
- string = NULL;
1618
- string_internal_stats_add(found_deleted_on_insert, 1);
1619
- }
2067
+ // the item we just did
2068
+ DICTIONARY_ITEM *item = dfe->item;
2069
1621
- string_stats_atomic_increment(searches);
2070
+ // release it, so that it can possibly be deleted
2071
+ if(likely(item)) {
2072
+ item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dfe->dict, item, dfe->rw);
2073
+ // item_release(dfe->dict, item);
2074
}
2075
1624
- netdata_rwlock_unlock(&string_base.rwlock);
1625
- return string;
1626
-}
2076
+ if(likely(dfe->rw != DICTIONARY_LOCK_REENTRANT))
2077
+ ll_recursive_unlock(dfe->dict, dfe->rw);
2078
1628
-// delete an entry from the index
1629
-static inline void string_index_delete(STRING *string) {
1630
- netdata_rwlock_wrlock(&string_base.rwlock);
2079
+ dfe->dict = NULL;
2080
+ dfe->item = NULL;
2081
+ dfe->name = NULL;
2082
+ dfe->value = NULL;
2083
+ dfe->counter = 0;
2084
+}
2085
1632
-#ifdef NETDATA_INTERNAL_CHECKS
1633
- if(unlikely(__atomic_load_n(&string->refcount, __ATOMIC_SEQ_CST) != 0))
1634
- fatal("STRING: tried to delete a string at %s() that is already freed (it has %d references).", __FUNCTION__, string->refcount);
1635
-#endif
2086
+// ----------------------------------------------------------------------------
2087
+// API - walk through the dictionary.
2088
+// The dictionary is locked for reading while this happens
2089
+// do not use other dictionary calls while walking the dictionary - deadlock!
2090
1637
- bool deleted = false;
2091
+int dictionary_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const DICTIONARY_ITEM *item, void *entry, void *data), void *data) {
2092
+ if(unlikely(!dict || !callback)) return 0;
2093
1639
- if (likely(string_base.JudyHSArray)) {
1640
- JError_t J_Error;
1641
- int ret = JudyHSDel(&string_base.JudyHSArray, (void *)string->str, string->length, &J_Error);
1642
- if (unlikely(ret == JERR)) {
1643
- error(
1644
- "STRING: Cannot delete entry with name '%s' from JudyHS, JU_ERRNO_* == %u, ID == %d",
1645
- string->str,
1646
- JU_ERRNO(&J_Error),
1647
- JU_ERRID(&J_Error));
1648
- } else
1649
- deleted = true;
2094
+ if(unlikely(is_dictionary_destroyed(dict))) {
2095
+ internal_error(true, "DICTIONARY: attempted to dictionary_walkthrough_rw() on a destroyed dictionary");
2096
+ return 0;
2097
}
2098
1652
- if (unlikely(!deleted))
1653
- error("STRING: tried to delete '%s' that is not in the index. Ignoring it.", string->str);
1654
- else {
1655
- size_t mem_size = sizeof(STRING) + string->length;
1656
- string_base.deletes++;
1657
- string_base.entries--;
1658
- string_base.memory -= (long)mem_size;
1659
- freez(string);
1660
- }
2099
+ ll_recursive_lock(dict, rw);
2100
1662
- netdata_rwlock_unlock(&string_base.rwlock);
1663
-}
2101
+ DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
2102
1665
-STRING *string_strdupz(const char *str) {
1666
- if(unlikely(!str || !*str)) return NULL;
2103
+ // written in such a way, that the callback can delete the active element
2104
1668
- size_t length = strlen(str) + 1;
1669
- STRING *string = string_index_search(str, length);
2105
+ int ret = 0;
2106
+ DICTIONARY_ITEM *item = dict->items.list, *item_next;
2107
+ while(item) {
2108
1671
- while(!string) {
1672
- // The search above did not find anything,
1673
- // We loop here, because during insert we may find an entry that is being deleted by another thread.
1674
- // So, we have to let it go and retry to insert it again.
2109
+ // skip the deleted items
2110
+ if(unlikely(!item_check_and_acquire(dict, item))) {
2111
+ item = item->next;
2112
+ continue;
2113
+ }
2114
1676
- string = string_index_insert(str, length);
1677
- }
2115
+ if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
2116
+ ll_recursive_unlock(dict, rw);
2117
1679
- // statistics
1680
- string_stats_atomic_increment(active_references);
2118
+ int r = callback(item, item->shared->value, data);
2119
1682
- return string;
1683
-}
2120
+ if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
2121
+ ll_recursive_lock(dict, rw);
2122
1685
-void string_freez(STRING *string) {
1686
- if(unlikely(!string)) return;
2123
+ // since we have a reference counter, this item cannot be deleted
2124
+ // until we release the reference counter, so the pointers are there
2125
+ item_next = item->next;
2126
1688
- int32_t refcount = string_entry_release(string);
2127
+ item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dict, item, rw);
2128
+ // item_release(dict, item);
2129
1690
-#ifdef NETDATA_INTERNAL_CHECKS
1691
- if(unlikely(refcount < 0))
1692
- fatal("STRING: tried to %s() a string that is already freed (it has %d references).", __FUNCTION__, string->refcount);
1693
-#endif
2130
+ if(unlikely(r < 0)) {
2131
+ ret = r;
2132
+ break;
2133
+ }
2134
+
2135
+ ret += r;
2136
1695
- if(unlikely(refcount == 0))
1696
- string_index_delete(string);
2137
+ item = item_next;
2138
+ }
2139
1698
- // statistics
1699
- string_stats_atomic_decrement(active_references);
1700
- string_stats_atomic_increment(releases);
1701
-}
2140
+ ll_recursive_unlock(dict, rw);
2141
1703
-size_t string_strlen(STRING *string) {
1704
- if(unlikely(!string)) return 0;
1705
- return string->length - 1;
2142
+ return ret;
2143
}
2144
1708
-const char *string2str(STRING *string) {
1709
- if(unlikely(!string)) return "";
1710
- return string->str;
2145
+// ----------------------------------------------------------------------------
2146
+// sorted walkthrough
2147
+
2148
+static int dictionary_sort_compar(const void *item1, const void *item2) {
2149
+ return strcmp(item_get_name((*(DICTIONARY_ITEM **)item1)), item_get_name((*(DICTIONARY_ITEM **)item2)));
2150
}
2151
1713
-STRING *string_2way_merge(STRING *a, STRING *b) {
1714
- static STRING *X = NULL;
2152
+int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const DICTIONARY_ITEM *item, void *entry, void *data), void *data) {
2153
+ if(unlikely(!dict || !callback)) return 0;
2154
+
2155
+ if(unlikely(is_dictionary_destroyed(dict))) {
2156
+ internal_error(true, "DICTIONARY: attempted to dictionary_sorted_walkthrough_rw() on a destroyed dictionary");
2157
+ return 0;
2158
+ }
2159
+
2160
+ DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
2161
+
2162
+ ll_recursive_lock(dict, rw);
2163
+ size_t entries = __atomic_load_n(&dict->entries, __ATOMIC_SEQ_CST);
2164
+ DICTIONARY_ITEM **array = mallocz(sizeof(DICTIONARY_ITEM *) * entries);
2165
1716
- if(unlikely(!X)) {
1717
- X = string_strdupz("[x]");
2166
+ size_t i;
2167
+ DICTIONARY_ITEM *item;
2168
+ for(item = dict->items.list, i = 0; item && i < entries; item = item->next) {
2169
+ if(likely(item_check_and_acquire(dict, item)))
2170
+ array[i++] = item;
2171
}
2172
+ ll_recursive_unlock(dict, rw);
2173
1720
- if(unlikely(a == b)) return string_dup(a);
1721
- if(unlikely(a == X)) return string_dup(a);
1722
- if(unlikely(b == X)) return string_dup(b);
1723
- if(unlikely(!a)) return string_dup(X);
1724
- if(unlikely(!b)) return string_dup(X);
2174
+ if(unlikely(i != entries))
2175
+ entries = i;
2176
1726
- size_t alen = string_strlen(a);
1727
- size_t blen = string_strlen(b);
1728
- size_t length = alen + blen + string_strlen(X) + 1;
1729
- char buf1[length + 1], buf2[length + 1], *dst1;
1730
- const char *s1, *s2;
2177
+ qsort(array, entries, sizeof(DICTIONARY_ITEM *), dictionary_sort_compar);
2178
1732
- s1 = string2str(a);
1733
- s2 = string2str(b);
1734
- dst1 = buf1;
1735
- for( ; *s1 && *s2 && *s1 == *s2 ;s1++, s2++)
1736
- *dst1++ = *s1;
2179
+ bool callit = true;
2180
+ int ret = 0, r;
2181
+ for(i = 0; i < entries ;i++) {
2182
+ item = array[i];
2183
1738
- *dst1 = '\0';
2184
+ if(callit)
2185
+ r = callback(item, item->shared->value, data);
2186
1740
- if(*s1 != '\0' || *s2 != '\0') {
1741
- *dst1++ = '[';
1742
- *dst1++ = 'x';
1743
- *dst1++ = ']';
2187
+ item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dict, item, rw);
2188
+ // item_release(dict, item);
2189
1745
- s1 = &(string2str(a))[alen - 1];
1746
- s2 = &(string2str(b))[blen - 1];
1747
- char *dst2 = &buf2[length];
1748
- *dst2 = '\0';
1749
- for (; *s1 && *s2 && *s1 == *s2; s1--, s2--)
1750
- *(--dst2) = *s1;
2190
+ if(r < 0) {
2191
+ ret = r;
2192
+ r = 0;
2193
1752
- strcpy(dst1, dst2);
2194
+ // stop calling the callback,
2195
+ // but we have to continue, to release all the reference counters
2196
+ callit = false;
2197
+ }
2198
+ else
2199
+ ret += r;
2200
}
2201
1755
- return string_strdupz(buf1);
2202
+ freez(array);
2203
+
2204
+ return ret;
2205
}
2206
2207
// ----------------------------------------------------------------------------
2294
static size_t dictionary_unittest_set_null(DICTIONARY *dict, char **names, char **values, size_t entries) {
2295
(void)values;
2296
size_t errors = 0;
1848
- long i = 0;
1849
- for(; i < (long)entries ;i++) {
2297
+ size_t i = 0;
2298
+ for(; i < entries ;i++) {
2299
void *val = dictionary_set(dict, names[i], NULL, 0);
2300
if(val != NULL) { fprintf(stderr, ">>> %s() returns a non NULL value\n", __FUNCTION__); errors++; }
2301
}
1853
- if(dictionary_stats_entries(dict) != i) {
2302
+ if(dictionary_entries(dict) != i) {
2303
fprintf(stderr, ">>> %s() dictionary items do not match\n", __FUNCTION__);
2304
errors++;
2305
}
2351
(void)names;
2352
size_t errors = 0;
2353
for(size_t i = 0; i < entries ;i++) {
1905
- int ret = dictionary_del(dict, values[i]);
1906
- if(ret != -1) { fprintf(stderr, ">>> %s() deleted non-existing item\n", __FUNCTION__); errors++; }
2354
+ bool ret = dictionary_del(dict, values[i]);
2355
+ if(ret) { fprintf(stderr, ">>> %s() deleted non-existing item\n", __FUNCTION__); errors++; }
2356
}
2357
return errors;
2358
}
2366
size_t backward_from = middle_to, backward_to = entries;
2367
2368
for(size_t i = forward_from; i < forward_to ;i++) {
1920
- int ret = dictionary_del(dict, names[i]);
1921
- if(ret == -1) { fprintf(stderr, ">>> %s() didn't delete (forward) existing item\n", __FUNCTION__); errors++; }
2369
+ bool ret = dictionary_del(dict, names[i]);
2370
+ if(!ret) { fprintf(stderr, ">>> %s() didn't delete (forward) existing item\n", __FUNCTION__); errors++; }
2371
}
2372
2373
for(size_t i = middle_to - 1; i >= middle_from ;i--) {
1925
- int ret = dictionary_del(dict, names[i]);
1926
- if(ret == -1) { fprintf(stderr, ">>> %s() didn't delete (middle) existing item\n", __FUNCTION__); errors++; }
2374
+ bool ret = dictionary_del(dict, names[i]);
2375
+ if(!ret) { fprintf(stderr, ">>> %s() didn't delete (middle) existing item\n", __FUNCTION__); errors++; }
2376
}
2377
2378
for(size_t i = backward_to - 1; i >= backward_from ;i--) {
1930
- int ret = dictionary_del(dict, names[i]);
1931
- if(ret == -1) { fprintf(stderr, ">>> %s() didn't delete (backward) existing item\n", __FUNCTION__); errors++; }
2379
+ bool ret = dictionary_del(dict, names[i]);
2380
+ if(!ret) { fprintf(stderr, ">>> %s() didn't delete (backward) existing item\n", __FUNCTION__); errors++; }
2381
}
2382
2383
return errors;
2420
return errors;
2421
}
2422
1974
-static int dictionary_unittest_walkthrough_callback(const char *name, void *value, void *data) {
1975
- (void)name;
1976
- (void)value;
1977
- (void)data;
2423
+static int dictionary_unittest_walkthrough_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
2424
return 1;
2425
}
2426
2432
else return sum - entries;
2433
}
2434
1989
-static int dictionary_unittest_walkthrough_delete_this_callback(const char *name, void *value, void *data) {
1990
- (void)value;
2435
+static int dictionary_unittest_walkthrough_delete_this_callback(const DICTIONARY_ITEM *item, void *value __maybe_unused, void *data) {
2436
+ const char *name = dictionary_acquired_item_name((DICTIONARY_ITEM *)item);
2437
1992
- if(dictionary_del_having_write_lock((DICTIONARY *)data, name) == -1)
2438
+ if(!dictionary_del((DICTIONARY *)data, name))
2439
return 0;
2440
2441
return 1;
2449
else return sum - entries;
2450
}
2451
2006
-static int dictionary_unittest_walkthrough_stop_callback(const char *name, void *value, void *data) {
2007
- (void)name;
2008
- (void)value;
2009
- (void)data;
2452
+static int dictionary_unittest_walkthrough_stop_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
2453
return -1;
2454
}
2455
2483
size_t count = 0;
2484
char *item;
2485
dfe_start_write(dict, item)
2043
- if(dictionary_del_having_write_lock(dict, item_name) != -1) count++;
2486
+ if(dictionary_del(dict, item_dfe.name)) count++;
2487
dfe_done(item);
2488
2489
if(count > entries) return count - entries;
2509
2510
if(callback == dictionary_unittest_destroy) dict = NULL;
2511
2069
- fprintf(stderr, " %zu errors, %ld items in dictionary, %llu usec \n", errs, dict? dictionary_stats_entries(dict):0, dt);
2512
+ long int found_ok = 0, found_deleted = 0, found_referenced = 0;
2513
+ if(dict) {
2514
+ DICTIONARY_ITEM *item;
2515
+ DOUBLE_LINKED_LIST_FOREACH_FORWARD(dict->items.list, item, prev, next) {
2516
+ if(item->refcount >= 0 && !(item ->flags & ITEM_FLAG_DELETED))
2517
+ found_ok++;
2518
+ else
2519
+ found_deleted++;
2520
+
2521
+ if(item->refcount > 0)
2522
+ found_referenced++;
2523
+ }
2524
+ }
2525
+
2526
+ fprintf(stderr, " %zu errors, %ld (found %ld) items in dictionary, %ld (found %ld) referenced, %ld (found %ld) deleted, %llu usec \n",
2527
+ errs, dict?dict->entries:0, found_ok, dict?dict->referenced_items:0, found_referenced, dict?dict->pending_deletion_items:0, found_deleted, dt);
2528
*errors += errs;
2529
return dt;
2530
}
2560
}
2561
2562
struct dictionary_unittest_sorting {
2105
- const char *oldname;
2106
- const char *oldvalue;
2563
+ const char *old_name;
2564
+ const char *old_value;
2565
size_t count;
2566
};
2567
2110
-static int dictionary_unittest_sorting_callback(const char *name, void *value, void *data) {
2568
+static int dictionary_unittest_sorting_callback(const DICTIONARY_ITEM *item, void *value, void *data) {
2569
+ const char *name = dictionary_acquired_item_name((DICTIONARY_ITEM *)item);
2570
struct dictionary_unittest_sorting *t = (struct dictionary_unittest_sorting *)data;
2571
const char *v = (const char *)value;
2572
2573
int ret = 0;
2115
- if(t->oldname && strcmp(t->oldname, name) > 0) {
2116
- fprintf(stderr, "name '%s' should be after '%s'\n", t->oldname, name);
2574
+ if(t->old_name && strcmp(t->old_name, name) > 0) {
2575
+ fprintf(stderr, "name '%s' should be after '%s'\n", t->old_name, name);
2576
ret = 1;
2577
}
2578
t->count++;
2120
- t->oldname = name;
2121
- t->oldvalue = v;
2579
+ t->old_name = name;
2580
+ t->old_value = v;
2581
2582
return ret;
2583
}
2585
static size_t dictionary_unittest_sorted_walkthrough(DICTIONARY *dict, char **names, char **values, size_t entries) {
2586
(void)names;
2587
(void)values;
2129
- struct dictionary_unittest_sorting tmp = { .oldname = NULL, .oldvalue = NULL, .count = 0 };
2588
+ struct dictionary_unittest_sorting tmp = { .old_name = NULL, .old_value = NULL, .count = 0 };
2589
size_t errors;
2590
errors = dictionary_sorted_walkthrough_read(dict, dictionary_unittest_sorting_callback, &tmp);
2591
2607
}
2608
2609
2151
-static int check_dictionary_callback(const char *name, void *value, void *data) {
2152
- (void)name;
2153
- (void)value;
2154
- (void)data;
2610
+static int unittest_check_dictionary_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
2611
return 1;
2612
}
2613
2158
-static size_t check_dictionary(DICTIONARY *dict, size_t entries, size_t linked_list_members) {
2614
+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) {
2615
size_t errors = 0;
2616
2161
- fprintf(stderr, "dictionary entries %ld, expected %zu...\t\t\t\t\t", dictionary_stats_entries(dict), entries);
2162
- if (dictionary_stats_entries(dict) != (long)entries) {
2617
+ size_t ll = 0;
2618
+ void *t;
2619
+ dfe_start_read(dict, t)
2620
+ ll++;
2621
+ dfe_done(t);
2622
+
2623
+ fprintf(stderr, "DICT %-20s: dictionary foreach entries %zu, expected %zu...\t\t\t\t\t",
2624
+ label, ll, traversable);
2625
+ if(ll != traversable) {
2626
fprintf(stderr, "FAILED\n");
2627
errors++;
2628
}
2629
else
2630
fprintf(stderr, "OK\n");
2631
2169
- size_t ll = 0;
2170
- void *t;
2171
- dfe_start_read(dict, t)
2172
- ll++;
2173
- dfe_done(t);
2632
+ ll = dictionary_walkthrough_read(dict, unittest_check_dictionary_callback, NULL);
2633
+ fprintf(stderr, "DICT %-20s: dictionary walkthrough entries %zu, expected %zu...\t\t\t\t",
2634
+ label, ll, traversable);
2635
+ if(ll != traversable) {
2636
+ fprintf(stderr, "FAILED\n");
2637
+ errors++;
2638
+ }
2639
+ else
2640
+ fprintf(stderr, "OK\n");
2641
2175
- fprintf(stderr, "dictionary foreach entries %zu, expected %zu...\t\t\t\t", ll, entries);
2176
- if(ll != entries) {
2642
+ ll = dictionary_sorted_walkthrough_read(dict, unittest_check_dictionary_callback, NULL);
2643
+ fprintf(stderr, "DICT %-20s: dictionary sorted walkthrough entries %zu, expected %zu...\t\t\t",
2644
+ label, ll, traversable);
2645
+ if(ll != traversable) {
2646
fprintf(stderr, "FAILED\n");
2647
errors++;
2648
}
2649
else
2650
fprintf(stderr, "OK\n");
2651
2183
- ll = dictionary_walkthrough_read(dict, check_dictionary_callback, NULL);
2184
- fprintf(stderr, "dictionary walkthrough entries %zu, expected %zu...\t\t\t\t", ll, entries);
2185
- if(ll != entries) {
2652
+ DICTIONARY_ITEM *item;
2653
+ size_t active = 0, deleted = 0, referenced = 0, pending = 0;
2654
+ for(item = dict->items.list; item; item = item->next) {
2655
+ if(!(item->flags & ITEM_FLAG_DELETED) && !(item->shared->flags & ITEM_FLAG_DELETED))
2656
+ active++;
2657
+ else {
2658
+ deleted++;
2659
+
2660
+ if(item->refcount == 0)
2661
+ pending++;
2662
+ }
2663
+
2664
+ if(item->refcount > 0)
2665
+ referenced++;
2666
+ }
2667
+
2668
+ fprintf(stderr, "DICT %-20s: dictionary active items reported %ld, counted %zu, expected %zu...\t\t\t",
2669
+ label, dict->entries, active, active_items);
2670
+ if(active != active_items || active != (size_t)dict->entries) {
2671
fprintf(stderr, "FAILED\n");
2672
errors++;
2673
}
2674
else
2675
fprintf(stderr, "OK\n");
2676
2192
- ll = dictionary_sorted_walkthrough_read(dict, check_dictionary_callback, NULL);
2193
- fprintf(stderr, "dictionary sorted walkthrough entries %zu, expected %zu...\t\t\t", ll, entries);
2194
- if(ll != entries) {
2677
+ fprintf(stderr, "DICT %-20s: dictionary deleted items counted %zu, expected %zu...\t\t\t\t",
2678
+ label, deleted, deleted_items);
2679
+ if(deleted != deleted_items) {
2680
fprintf(stderr, "FAILED\n");
2681
errors++;
2682
}
2683
else
2684
fprintf(stderr, "OK\n");
2685
2201
- NAME_VALUE *nv;
2202
- for(ll = 0, nv = dict->first_item; nv ;nv = nv->next)
2203
- ll++;
2686
+ fprintf(stderr, "DICT %-20s: dictionary referenced items reported %ld, counted %zu, expected %zu...\t\t",
2687
+ label, dict->referenced_items, referenced, referenced_items);
2688
+ if(referenced != referenced_items || dict->referenced_items != (long int)referenced) {
2689
+ fprintf(stderr, "FAILED\n");
2690
+ errors++;
2691
+ }
2692
+ else
2693
+ fprintf(stderr, "OK\n");
2694
2205
- fprintf(stderr, "dictionary linked list entries %zu, expected %zu...\t\t\t\t", ll, linked_list_members);
2206
- if(ll != linked_list_members) {
2695
+ fprintf(stderr, "DICT %-20s: dictionary pending deletion items reported %ld, counted %zu, expected %zu...\t",
2696
+ label, dict->pending_deletion_items, pending, pending_deletion);
2697
+ if(pending != pending_deletion || pending != (size_t)dict->pending_deletion_items) {
2698
fprintf(stderr, "FAILED\n");
2699
errors++;
2700
}
2704
return errors;
2705
}
2706
2216
-static int check_name_value_callback(const char *name, void *value, void *data) {
2217
- (void)name;
2707
+static int check_item_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
2708
return value == data;
2709
}
2710
2221
-static size_t check_name_value_deleted_flag(DICTIONARY *dict, NAME_VALUE *nv, const char *name, const char *value, unsigned refcount, NAME_VALUE_FLAGS deleted_flags, bool searchable, bool browsable, bool linked) {
2711
+static size_t unittest_check_item(const char *label, DICTIONARY *dict,
2712
+ DICTIONARY_ITEM *item, const char *name, const char *value, int refcount,
2713
+ ITEM_FLAGS deleted_flags, bool searchable, bool browsable, bool linked) {
2714
size_t errors = 0;
2715
2224
- fprintf(stderr, "NAME_VALUE name is '%s', expected '%s'...\t\t\t\t", namevalue_get_name(nv), name);
2225
- if(strcmp(namevalue_get_name(nv), name) != 0) {
2716
+ fprintf(stderr, "ITEM %-20s: name is '%s', expected '%s'...\t\t\t\t\t\t", label, item_get_name(item), name);
2717
+ if(strcmp(item_get_name(item), name) != 0) {
2718
fprintf(stderr, "FAILED\n");
2719
errors++;
2720
}
2721
else
2722
fprintf(stderr, "OK\n");
2723
2232
- fprintf(stderr, "NAME_VALUE value is '%s', expected '%s'...\t\t\t", (const char *)nv->value, value);
2233
- if(strcmp((const char *)nv->value, value) != 0) {
2724
+ fprintf(stderr, "ITEM %-20s: value is '%s', expected '%s'...\t\t\t\t\t", label, (const char *)item->shared->value, value);
2725
+ if(strcmp((const char *)item->shared->value, value) != 0) {
2726
fprintf(stderr, "FAILED\n");
2727
errors++;
2728
}
2729
else
2730
fprintf(stderr, "OK\n");
2731
2240
- fprintf(stderr, "NAME_VALUE refcount is %u, expected %u...\t\t\t\t\t", nv->refcount, refcount);
2241
- if (nv->refcount != refcount) {
2732
+ fprintf(stderr, "ITEM %-20s: refcount is %d, expected %d...\t\t\t\t\t\t\t", label, item->refcount, refcount);
2733
+ if (item->refcount != refcount) {
2734
fprintf(stderr, "FAILED\n");
2735
errors++;
2736
}
2737
else
2738
fprintf(stderr, "OK\n");
2739
2248
- fprintf(stderr, "NAME_VALUE deleted flag is %s, expected %s...\t\t\t", (nv->flags & NAME_VALUE_FLAG_DELETED)?"TRUE":"FALSE", (deleted_flags & NAME_VALUE_FLAG_DELETED)?"TRUE":"FALSE");
2249
- if ((nv->flags & NAME_VALUE_FLAG_DELETED) != (deleted_flags & NAME_VALUE_FLAG_DELETED)) {
2740
+ fprintf(stderr, "ITEM %-20s: deleted flag is %s, expected %s...\t\t\t\t\t", label,
2741
+ (item->flags & ITEM_FLAG_DELETED || item->shared->flags & ITEM_FLAG_DELETED)?"true":"false",
2742
+ (deleted_flags & ITEM_FLAG_DELETED)?"true":"false");
2743
+
2744
+ if ((item->flags & ITEM_FLAG_DELETED || item->shared->flags & ITEM_FLAG_DELETED) != (deleted_flags & ITEM_FLAG_DELETED)) {
2745
fprintf(stderr, "FAILED\n");
2746
errors++;
2747
}
2749
fprintf(stderr, "OK\n");
2750
2751
void *v = dictionary_get(dict, name);
2257
- bool found = v == nv->value;
2258
- fprintf(stderr, "NAME_VALUE searchable %5s, expected %5s...\t\t\t\t", found?"true":"false", searchable?"true":"false");
2752
+ bool found = v == item->shared->value;
2753
+ fprintf(stderr, "ITEM %-20s: searchable %5s, expected %5s...\t\t\t\t\t\t", label,
2754
+ found?"true":"false", searchable?"true":"false");
2755
if(found != searchable) {
2756
fprintf(stderr, "FAILED\n");
2757
errors++;
2762
found = false;
2763
void *t;
2764
dfe_start_read(dict, t) {
2269
- if(t == nv->value) found = true;
2765
+ if(t == item->shared->value) found = true;
2766
}
2767
dfe_done(t);
2768
2273
- fprintf(stderr, "NAME_VALUE dfe browsable %5s, expected %5s...\t\t\t", found?"true":"false", browsable?"true":"false");
2769
+ fprintf(stderr, "ITEM %-20s: dfe browsable %5s, expected %5s...\t\t\t\t\t", label,
2770
+ found?"true":"false", browsable?"true":"false");
2771
if(found != browsable) {
2772
fprintf(stderr, "FAILED\n");
2773
errors++;
2775
else
2776
fprintf(stderr, "OK\n");
2777
2281
- found = dictionary_walkthrough_read(dict, check_name_value_callback, nv->value);
2282
- fprintf(stderr, "NAME_VALUE walkthrough browsable %5s, expected %5s...\t\t", found?"true":"false", browsable?"true":"false");
2778
+ found = dictionary_walkthrough_read(dict, check_item_callback, item->shared->value);
2779
+ fprintf(stderr, "ITEM %-20s: walkthrough browsable %5s, expected %5s...\t\t\t\t", label,
2780
+ found?"true":"false", browsable?"true":"false");
2781
if(found != browsable) {
2782
fprintf(stderr, "FAILED\n");
2783
errors++;
2785
else
2786
fprintf(stderr, "OK\n");
2787
2290
- found = dictionary_sorted_walkthrough_read(dict, check_name_value_callback, nv->value);
2291
- fprintf(stderr, "NAME_VALUE sorted walkthrough browsable %5s, expected %5s...\t", found?"true":"false", browsable?"true":"false");
2788
+ found = dictionary_sorted_walkthrough_read(dict, check_item_callback, item->shared->value);
2789
+ fprintf(stderr, "ITEM %-20s: sorted walkthrough browsable %5s, expected %5s...\t\t\t", label,
2790
+ found?"true":"false", browsable?"true":"false");
2791
if(found != browsable) {
2792
fprintf(stderr, "FAILED\n");
2793
errors++;
2796
fprintf(stderr, "OK\n");
2797
2798
found = false;
2300
- NAME_VALUE *n;
2301
- for(n = dict->first_item; n ;n = n->next)
2302
- if(n == nv) found = true;
2799
+ DICTIONARY_ITEM *n;
2800
+ for(n = dict->items.list; n ;n = n->next)
2801
+ if(n == item) found = true;
2802
2304
- fprintf(stderr, "NAME_VALUE linked %5s, expected %5s...\t\t\t\t", found?"true":"false", linked?"true":"false");
2803
+ fprintf(stderr, "ITEM %-20s: linked %5s, expected %5s...\t\t\t\t\t\t", label,
2804
+ found?"true":"false", linked?"true":"false");
2805
if(found != linked) {
2806
fprintf(stderr, "FAILED\n");
2807
errors++;
2812
return errors;
2813
}
2814
2315
-static int string_threads_join = 0;
2316
-static void *string_thread(void *arg __maybe_unused) {
2317
- int dups = 1; //(gettid() % 10);
2815
+struct thread_unittest {
2816
+ int join;
2817
+ DICTIONARY *dict;
2818
+ int dups;
2819
+};
2820
+
2821
+static void *unittest_dict_thread(void *arg) {
2822
+ struct thread_unittest *tu = arg;
2823
for(; 1 ;) {
2319
- if(string_threads_join)
2824
+ if(__atomic_load_n(&tu->join, __ATOMIC_RELAXED))
2825
break;
2826
2322
- STRING *s = string_strdupz("string thread checking 1234567890");
2827
+ DICT_ITEM_CONST DICTIONARY_ITEM *item =
2828
+ dictionary_set_and_acquire_item_advanced(tu->dict, "dict thread checking 1234567890",
2829
+ -1, NULL, 0, NULL);
2830
+
2831
+
2832
+ dictionary_get(tu->dict, dictionary_acquired_item_name(item));
2833
+
2834
+ void *t1;
2835
+ dfe_start_write(tu->dict, t1) {
2836
+
2837
+ // this should delete the referenced item
2838
+ dictionary_del(tu->dict, t1_dfe.name);
2839
+
2840
+ void *t2;
2841
+ dfe_start_write(tu->dict, t2) {
2842
+ // this should add another
2843
+ dictionary_set(tu->dict, t2_dfe.name, NULL, 0);
2844
+
2845
+ dictionary_get(tu->dict, dictionary_acquired_item_name(item));
2846
+
2847
+ // and this should delete it again
2848
+ dictionary_del(tu->dict, t2_dfe.name);
2849
+ }
2850
+ dfe_done(t2);
2851
+
2852
+ // this should fail to add it
2853
+ dictionary_set(tu->dict, t1_dfe.name, NULL, 0);
2854
+ dictionary_del(tu->dict, t1_dfe.name);
2855
+ }
2856
+ dfe_done(t1);
2857
+
2858
+ for(int i = 0; i < tu->dups ; i++) {
2859
+ dictionary_acquired_item_dup(tu->dict, item);
2860
+ dictionary_get(tu->dict, dictionary_acquired_item_name(item));
2861
+ }
2862
+
2863
+ for(int i = 0; i < tu->dups ; i++) {
2864
+ dictionary_acquired_item_release(tu->dict, item);
2865
+ dictionary_del(tu->dict, dictionary_acquired_item_name(item));
2866
+ }
2867
+
2868
+ dictionary_acquired_item_release(tu->dict, item);
2869
+
2870
+ dictionary_del(tu->dict, "dict thread checking 1234567890");
2871
+ }
2872
+
2873
+ return arg;
2874
+}
2875
+
2876
+static int dictionary_unittest_threads() {
2877
+
2878
+ struct thread_unittest tu = {
2879
+ .join = 0,
2880
+ .dict = NULL,
2881
+ .dups = 1,
2882
+ };
2883
+
2884
+ // threads testing of dictionary
2885
+ tu.dict = dictionary_create(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
2886
+ time_t seconds_to_run = 5;
2887
+ int threads_to_create = 2;
2888
+ fprintf(
2889
+ stderr,
2890
+ "\nChecking dictionary concurrency with %d threads for %ld seconds...\n",
2891
+ threads_to_create,
2892
+ seconds_to_run);
2893
+
2894
+ netdata_thread_t threads[threads_to_create];
2895
+ tu.join = 0;
2896
+ for (int i = 0; i < threads_to_create; i++) {
2897
+ char buf[100 + 1];
2898
+ snprintf(buf, 100, "dict%d", i);
2899
+ netdata_thread_create(
2900
+ &threads[i],
2901
+ buf,
2902
+ NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
2903
+ unittest_dict_thread,
2904
+ &tu);
2905
+ }
2906
+ sleep_usec(seconds_to_run * USEC_PER_SEC);
2907
+
2908
+ __atomic_store_n(&tu.join, 1, __ATOMIC_RELAXED);
2909
+ for (int i = 0; i < threads_to_create; i++) {
2910
+ void *retval;
2911
+ netdata_thread_join(threads[i], &retval);
2912
+ }
2913
+
2914
+ fprintf(stderr,
2915
+ "inserts %zu"
2916
+ ", deletes %zu"
2917
+ ", searches %zu"
2918
+ ", resets %zu"
2919
+ ", entries %ld"
2920
+ ", referenced_items %ld"
2921
+ ", pending deletions %ld"
2922
+ ", check spins %zu"
2923
+ ", insert spins %zu"
2924
+ ", search ignores %zu"
2925
+ "\n",
2926
+ tu.dict->stats->ops.inserts,
2927
+ tu.dict->stats->ops.deletes,
2928
+ tu.dict->stats->ops.searches,
2929
+ tu.dict->stats->ops.resets,
2930
+ tu.dict->entries,
2931
+ tu.dict->referenced_items,
2932
+ tu.dict->pending_deletion_items,
2933
+ tu.dict->stats->spin_locks.use,
2934
+ tu.dict->stats->spin_locks.insert,
2935
+ tu.dict->stats->spin_locks.search
2936
+ );
2937
+ dictionary_destroy(tu.dict);
2938
+ tu.dict = NULL;
2939
+
2940
+ return 0;
2941
+}
2942
+
2943
+struct thread_view_unittest {
2944
+ int join;
2945
+ DICTIONARY *master;
2946
+ DICTIONARY *view;
2947
+ DICTIONARY_ITEM *item_master;
2948
+ int dups;
2949
+};
2950
+
2951
+static void *unittest_dict_master_thread(void *arg) {
2952
+ struct thread_view_unittest *tv = arg;
2953
+
2954
+ while(!__atomic_load_n(&tv->join, __ATOMIC_SEQ_CST)) {
2955
+ if(__atomic_load_n(&tv->item_master, __ATOMIC_SEQ_CST) != NULL)
2956
+ continue;
2957
+
2958
+ DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(tv->master, "ITEM1", "123", strlen("123") + 1);
2959
+ dictionary_acquired_item_dup(tv->master, item);
2960
+ dictionary_del(tv->master, "ITEM1");
2961
+
2962
+ __atomic_store_n(&tv->item_master, item, __ATOMIC_SEQ_CST);
2963
+
2964
+ for(int i = 0; i < tv->dups ; i++) {
2965
+ dictionary_acquired_item_dup(tv->master, item);
2966
+ }
2967
+
2968
+ for(int i = 0; i < tv->dups ; i++) {
2969
+ dictionary_acquired_item_release(tv->master, item);
2970
+ }
2971
+
2972
+ dictionary_acquired_item_release(tv->master, item);
2973
+ }
2974
+
2975
+ return arg;
2976
+}
2977
+
2978
+static void *unittest_dict_view_thread(void *arg) {
2979
+ struct thread_view_unittest *tv = arg;
2980
+
2981
+ while(!__atomic_load_n(&tv->join, __ATOMIC_SEQ_CST)) {
2982
+ DICTIONARY_ITEM *m_item = __atomic_load_n(&tv->item_master, __ATOMIC_SEQ_CST);
2983
+ if(!m_item) continue;
2984
+
2985
+ DICTIONARY_ITEM *v_item = dictionary_view_set_and_acquire_item(tv->view, "ITEM2", m_item);
2986
+ dictionary_acquired_item_release(tv->master, m_item);
2987
+ __atomic_store_n(&tv->item_master, NULL, __ATOMIC_SEQ_CST);
2988
2324
- for(int i = 0; i < dups ; i++)
2325
- string_dup(s);
2989
+ for(int i = 0; i < tv->dups ; i++) {
2990
+ dictionary_acquired_item_dup(tv->view, v_item);
2991
+ }
2992
+
2993
+ for(int i = 0; i < tv->dups ; i++) {
2994
+ dictionary_acquired_item_release(tv->view, v_item);
2995
+ }
2996
2327
- for(int i = 0; i < dups ; i++)
2328
- string_freez(s);
2997
+ dictionary_del(tv->view, "ITEM2");
2998
2330
- string_freez(s);
2999
+ dictionary_acquired_item_release(tv->view, v_item);
3000
}
3001
3002
return arg;
3003
}
3004
3005
+static int dictionary_unittest_view_threads() {
3006
+
3007
+ struct thread_view_unittest tv = {
3008
+ .join = 0,
3009
+ .master = NULL,
3010
+ .view = NULL,
3011
+ .item_master = NULL,
3012
+ .dups = 1,
3013
+ };
3014
+
3015
+ // threads testing of dictionary
3016
+ struct dictionary_stats stats = {};
3017
+ tv.master = dictionary_create_advanced(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, &stats);
3018
+ tv.view = dictionary_create_view(tv.master);
3019
+
3020
+ time_t seconds_to_run = 5;
3021
+ fprintf(
3022
+ stderr,
3023
+ "\nChecking dictionary concurrency with 1 master and 1 view threads for %ld seconds...\n",
3024
+ seconds_to_run);
3025
+
3026
+ netdata_thread_t master_thread, view_thread;
3027
+ tv.join = 0;
3028
+
3029
+ netdata_thread_create(
3030
+ &master_thread,
3031
+ "master",
3032
+ NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
3033
+ unittest_dict_master_thread,
3034
+ &tv);
3035
+
3036
+ netdata_thread_create(
3037
+ &view_thread,
3038
+ "view",
3039
+ NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
3040
+ unittest_dict_view_thread,
3041
+ &tv);
3042
+
3043
+ sleep_usec(seconds_to_run * USEC_PER_SEC);
3044
+
3045
+ __atomic_store_n(&tv.join, 1, __ATOMIC_RELAXED);
3046
+ void *retval;
3047
+ netdata_thread_join(view_thread, &retval);
3048
+ netdata_thread_join(master_thread, &retval);
3049
+
3050
+ fprintf(stderr,
3051
+ "inserts %zu"
3052
+ ", deletes %zu"
3053
+ ", searches %zu"
3054
+ ", resets %zu"
3055
+ ", entries %ld (%ld on view)"
3056
+ ", referenced_items %ld (%ld on view)"
3057
+ ", pending deletions %ld (%ld on view)"
3058
+ ", check spins %zu"
3059
+ ", insert spins %zu"
3060
+ ", search ignores %zu"
3061
+ "\n",
3062
+ stats.ops.inserts,
3063
+ stats.ops.deletes,
3064
+ stats.ops.searches,
3065
+ stats.ops.resets,
3066
+ tv.master->entries, tv.view->entries,
3067
+ tv.master->referenced_items, tv.view->referenced_items,
3068
+ tv.master->pending_deletion_items, tv.view->pending_deletion_items,
3069
+ stats.spin_locks.use,
3070
+ stats.spin_locks.insert,
3071
+ stats.spin_locks.search
3072
+ );
3073
+ dictionary_destroy(tv.master);
3074
+ dictionary_destroy(tv.view);
3075
+
3076
+ return 0;
3077
+}
3078
+
3079
+size_t dictionary_unittest_views(void) {
3080
+ size_t errors = 0;
3081
+ struct dictionary_stats stats = {};
3082
+ DICTIONARY *master = dictionary_create_advanced(DICT_OPTION_NONE, &stats);
3083
+ DICTIONARY *view = dictionary_create_view(master);
3084
+
3085
+ fprintf(stderr, "\n\nChecking dictionary views...\n");
3086
+
3087
+ // Add an item to both master and view, then remove the view first and the master second
3088
+ fprintf(stderr, "\nPASS 1: Adding 1 item to master:\n");
3089
+ DICTIONARY_ITEM *item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1);
3090
+ errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3091
+ errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3092
+
3093
+ fprintf(stderr, "\nPASS 1: Adding master item to view:\n");
3094
+ DICTIONARY_ITEM *item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master);
3095
+ errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0);
3096
+ 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);
3097
+
3098
+ fprintf(stderr, "\nPASS 1: Deleting view item:\n");
3099
+ dictionary_del(view, "KEY 1 ON VIEW");
3100
+ errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3101
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
3102
+ errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3103
+ 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);
3104
+
3105
+ fprintf(stderr, "\nPASS 1: Releasing the deleted view item:\n");
3106
+ dictionary_acquired_item_release(view, item1_on_view);
3107
+ errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3108
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3109
+ errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3110
+
3111
+ fprintf(stderr, "\nPASS 1: Releasing the acquired master item:\n");
3112
+ dictionary_acquired_item_release(master, item1_on_master);
3113
+ errors += unittest_check_dictionary("master", master, 1, 1, 0, 0, 0);
3114
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3115
+ errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 0, ITEM_FLAG_NONE, true, true, true);
3116
+
3117
+ fprintf(stderr, "\nPASS 1: Deleting the released master item:\n");
3118
+ dictionary_del(master, "KEY 1");
3119
+ errors += unittest_check_dictionary("master", master, 0, 0, 0, 0, 0);
3120
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3121
+
3122
+ // The other way now:
3123
+ // Add an item to both master and view, then remove the master first and verify it is deleted on the view also
3124
+ fprintf(stderr, "\nPASS 2: Adding 1 item to master:\n");
3125
+ item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1);
3126
+ errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3127
+ errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3128
+
3129
+ fprintf(stderr, "\nPASS 2: Adding master item to view:\n");
3130
+ item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master);
3131
+ errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0);
3132
+ 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);
3133
+
3134
+ fprintf(stderr, "\nPASS 2: Deleting master item:\n");
3135
+ dictionary_del(master, "KEY 1");
3136
+ dictionary_version(view);
3137
+ errors += unittest_check_dictionary("master", master, 0, 0, 1, 1, 0);
3138
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
3139
+ errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true);
3140
+ 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);
3141
+
3142
+ fprintf(stderr, "\nPASS 2: Releasing the acquired master item:\n");
3143
+ dictionary_acquired_item_release(master, item1_on_master);
3144
+ errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1);
3145
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
3146
+ 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);
3147
+
3148
+ fprintf(stderr, "\nPASS 2: Releasing the deleted view item:\n");
3149
+ dictionary_acquired_item_release(view, item1_on_view);
3150
+ errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1);
3151
+ errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3152
+
3153
+ dictionary_destroy(master);
3154
+ dictionary_destroy(view);
3155
+ return errors;
3156
+}
3157
+
3158
int dictionary_unittest(size_t entries) {
3159
if(entries < 10) entries = 10;
3160
3166
char **values = dictionary_unittest_generate_values(entries);
3167
3168
fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
2347
- dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
3169
+ dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
3170
dictionary_unittest_clone(dict, names, values, entries, &errors);
3171
3172
fprintf(stderr, "\nCreating dictionary multi threaded, clone, %zu items\n", entries);
2351
- dict = dictionary_create(DICTIONARY_FLAG_NONE);
3173
+ dict = dictionary_create(DICT_OPTION_NONE);
3174
dictionary_unittest_clone(dict, names, values, entries, &errors);
3175
3176
fprintf(stderr, "\nCreating dictionary single threaded, non-clone, add-in-front options, %zu items\n", entries);
2355
- dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED|DICTIONARY_FLAG_NAME_LINK_DONT_CLONE|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE|DICTIONARY_FLAG_ADD_IN_FRONT);
3177
+ dict = dictionary_create(
3178
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE |
3179
+ DICT_OPTION_ADD_IN_FRONT);
3180
dictionary_unittest_nonclone(dict, names, values, entries, &errors);
3181
3182
fprintf(stderr, "\nCreating dictionary multi threaded, non-clone, add-in-front options, %zu items\n", entries);
2359
- dict = dictionary_create(DICTIONARY_FLAG_NAME_LINK_DONT_CLONE|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE|DICTIONARY_FLAG_ADD_IN_FRONT);
3183
+ dict = dictionary_create(
3184
+ DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_ADD_IN_FRONT);
3185
dictionary_unittest_nonclone(dict, names, values, entries, &errors);
3186
3187
fprintf(stderr, "\nCreating dictionary single-threaded, non-clone, don't overwrite options, %zu items\n", entries);
2363
- dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED|DICTIONARY_FLAG_NAME_LINK_DONT_CLONE|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE|DICTIONARY_FLAG_DONT_OVERWRITE_VALUE);
3188
+ dict = dictionary_create(
3189
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE |
3190
+ DICT_OPTION_DONT_OVERWRITE_VALUE);
3191
dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
3192
dictionary_unittest_run_and_measure_time(dict, "resetting non-overwrite entries", names, values, entries, &errors, dictionary_unittest_reset_dont_overwrite_nonclone);
3193
dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, &errors, dictionary_unittest_foreach);
3196
dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3197
3198
fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries);
2372
- dict = dictionary_create(DICTIONARY_FLAG_NAME_LINK_DONT_CLONE|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE|DICTIONARY_FLAG_DONT_OVERWRITE_VALUE);
3199
+ dict = dictionary_create(
3200
+ DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
3201
dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
3202
dictionary_unittest_run_and_measure_time(dict, "walkthrough write delete this", names, values, entries, &errors, dictionary_unittest_walkthrough_delete_this);
3203
dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3204
3205
fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries);
2378
- dict = dictionary_create(DICTIONARY_FLAG_NAME_LINK_DONT_CLONE|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE|DICTIONARY_FLAG_DONT_OVERWRITE_VALUE);
3206
+ dict = dictionary_create(
3207
+ DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
3208
dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
3209
dictionary_unittest_run_and_measure_time(dict, "foreach write delete this", names, values, entries, &errors, dictionary_unittest_foreach_delete_this);
3210
dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop empty", names, values, 0, &errors, dictionary_unittest_foreach);
3212
dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3213
3214
fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
2386
- dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
3215
+ dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
3216
dictionary_unittest_sorting(dict, names, values, entries, &errors);
3217
dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3218
3219
fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
2391
- dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
3220
+ dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
3221
dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
3222
dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3223
3224
fprintf(stderr, "\nCreating dictionary single threaded, noclone, %zu items\n", entries);
2396
- dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE);
3225
+ dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_VALUE_LINK_DONT_CLONE);
3226
dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
3227
dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3228
3229
// check reference counters
3230
{
3231
fprintf(stderr, "\nTesting reference counters:\n");
2403
- dict = dictionary_create(DICTIONARY_FLAG_NONE|DICTIONARY_FLAG_NAME_LINK_DONT_CLONE);
2404
- errors += check_dictionary(dict, 0, 0);
3232
+ dict = dictionary_create(DICT_OPTION_NONE | DICT_OPTION_NAME_LINK_DONT_CLONE);
3233
+ errors += unittest_check_dictionary("", dict, 0, 0, 0, 0, 0);
3234
3235
fprintf(stderr, "\nAdding test item to dictionary and acquiring it\n");
3236
dictionary_set(dict, "test", "ITEM1", 6);
2408
- NAME_VALUE *nv = (NAME_VALUE *)dictionary_get_and_acquire_item(dict, "test");
3237
+ DICTIONARY_ITEM *item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
3238
2410
- errors += check_dictionary(dict, 1, 1);
2411
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_NONE, true, true, true);
3239
+ errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3240
+ errors += unittest_check_item("ACQUIRED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
3241
3242
fprintf(stderr, "\nChecking that reference counters are increased:\n");
3243
void *t;
3244
dfe_start_read(dict, t) {
2416
- errors += check_dictionary(dict, 1, 1);
2417
- errors +=
2418
- check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 2, NAME_VALUE_FLAG_NONE, true, true, true);
3245
+ errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3246
+ errors += unittest_check_item("ACQUIRED TRAVERSAL", dict, item, "test", "ITEM1", 2, ITEM_FLAG_NONE, true, true, true);
3247
}
3248
dfe_done(t);
3249
3250
fprintf(stderr, "\nChecking that reference counters are decreased:\n");
2423
- errors += check_dictionary(dict, 1, 1);
2424
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_NONE, true, true, true);
3251
+ errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3252
+ errors += unittest_check_item("ACQUIRED TRAVERSAL 2", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
3253
3254
fprintf(stderr, "\nDeleting the item we have acquired:\n");
3255
dictionary_del(dict, "test");
3256
2429
- errors += check_dictionary(dict, 0, 1);
2430
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
3257
+ errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0);
3258
+ errors += unittest_check_item("DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3259
3260
fprintf(stderr, "\nAdding another item with the same name of the item we deleted, while being acquired:\n");
3261
dictionary_set(dict, "test", "ITEM2", 6);
2434
- errors += check_dictionary(dict, 1, 2);
3262
+ errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0);
3263
3264
fprintf(stderr, "\nAcquiring the second item:\n");
2437
- NAME_VALUE *nv2 = (NAME_VALUE *)dictionary_get_and_acquire_item(dict, "test");
2438
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
2439
- errors += check_name_value_deleted_flag(dict, nv2, "test", "ITEM2", 1, NAME_VALUE_FLAG_NONE, true, true, true);
3265
+ DICTIONARY_ITEM *item2 = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
3266
+ errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3267
+ errors += unittest_check_item("SECOND", dict, item2, "test", "ITEM2", 1, ITEM_FLAG_NONE, true, true, true);
3268
+ errors += unittest_check_dictionary("", dict, 1, 1, 1, 2, 0);
3269
3270
fprintf(stderr, "\nReleasing the second item (the first is still acquired):\n");
2442
- dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)nv2);
2443
- errors += check_dictionary(dict, 1, 2);
2444
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
2445
- errors += check_name_value_deleted_flag(dict, nv2, "test", "ITEM2", 0, NAME_VALUE_FLAG_NONE, true, true, true);
3271
+ dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item2);
3272
+ errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0);
3273
+ errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3274
+ errors += unittest_check_item("SECOND RELEASED", dict, item2, "test", "ITEM2", 0, ITEM_FLAG_NONE, true, true, true);
3275
3276
fprintf(stderr, "\nDeleting the second item (the first is still acquired):\n");
3277
dictionary_del(dict, "test");
2449
- errors += check_dictionary(dict, 0, 1);
2450
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
3278
+ errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0);
3279
+ errors += unittest_check_item("ACQUIRED DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3280
3281
fprintf(stderr, "\nReleasing the first item (which we have already deleted):\n");
2453
- dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)nv);
2454
- errors += check_dictionary(dict, 0, 0);
3282
+ dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item);
3283
+ dfe_start_write(dict, item) ; dfe_done(item);
3284
+ errors += unittest_check_dictionary("", dict, 0, 0, 1, 0, 1);
3285
3286
fprintf(stderr, "\nAdding again the test item to dictionary and acquiring it\n");
3287
dictionary_set(dict, "test", "ITEM1", 6);
2458
- nv = (NAME_VALUE *)dictionary_get_and_acquire_item(dict, "test");
3288
+ item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
3289
2460
- errors += check_dictionary(dict, 1, 1);
2461
- errors += check_name_value_deleted_flag(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_NONE, true, true, true);
3290
+ errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3291
+ errors += unittest_check_item("RE-ADDITION", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
3292
3293
fprintf(stderr, "\nDestroying the dictionary while we have acquired an item\n");
3294
dictionary_destroy(dict);
3295
3296
fprintf(stderr, "Releasing the item (on a destroyed dictionary)\n");
2467
- dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)nv);
2468
- nv = NULL;
3297
+ dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item);
3298
+ item = NULL;
3299
dict = NULL;
3300
}
3301
2472
- // check string
2473
- {
2474
- long int string_entries_starting = string_base.entries;
2475
-
2476
- fprintf(stderr, "\nChecking strings...\n");
2477
-
2478
- STRING *s1 = string_strdupz("hello unittest");
2479
- STRING *s2 = string_strdupz("hello unittest");
2480
- if(s1 != s2) {
2481
- errors++;
2482
- fprintf(stderr, "ERROR: duplicating strings are not deduplicated\n");
2483
- }
2484
- else
2485
- fprintf(stderr, "OK: duplicating string are deduplicated\n");
2486
-
2487
- STRING *s3 = string_dup(s1);
2488
- if(s3 != s1) {
2489
- errors++;
2490
- fprintf(stderr, "ERROR: cloning strings are not deduplicated\n");
2491
- }
2492
- else
2493
- fprintf(stderr, "OK: cloning string are deduplicated\n");
2494
-
2495
- if(s1->refcount != 3) {
2496
- errors++;
2497
- fprintf(stderr, "ERROR: string refcount is not 3\n");
2498
- }
2499
- else
2500
- fprintf(stderr, "OK: string refcount is 3\n");
2501
-
2502
- STRING *s4 = string_strdupz("world unittest");
2503
- if(s4 == s1) {
2504
- errors++;
2505
- fprintf(stderr, "ERROR: string is sharing pointers on different strings\n");
2506
- }
2507
- else
2508
- fprintf(stderr, "OK: string is properly handling different strings\n");
2509
-
2510
- usec_t start_ut, end_ut;
2511
- STRING **strings = mallocz(entries * sizeof(STRING *));
2512
-
2513
- start_ut = now_realtime_usec();
2514
- for(size_t i = 0; i < entries ;i++) {
2515
- strings[i] = string_strdupz(names[i]);
2516
- }
2517
- end_ut = now_realtime_usec();
2518
- fprintf(stderr, "Created %zu strings in %llu usecs\n", entries, end_ut - start_ut);
2519
-
2520
- start_ut = now_realtime_usec();
2521
- for(size_t i = 0; i < entries ;i++) {
2522
- strings[i] = string_dup(strings[i]);
2523
- }
2524
- end_ut = now_realtime_usec();
2525
- fprintf(stderr, "Cloned %zu strings in %llu usecs\n", entries, end_ut - start_ut);
2526
-
2527
- start_ut = now_realtime_usec();
2528
- for(size_t i = 0; i < entries ;i++) {
2529
- string_freez(strings[i]);
2530
- string_freez(strings[i]);
2531
- }
2532
- end_ut = now_realtime_usec();
2533
- fprintf(stderr, "Freed %zu strings in %llu usecs\n", entries, end_ut - start_ut);
2534
-
2535
- freez(strings);
2536
-
2537
- if(string_base.entries != string_entries_starting + 2) {
2538
- errors++;
2539
- fprintf(stderr, "ERROR: strings dictionary should have %ld items but it has %ld\n", string_entries_starting + 2, string_base.entries);
2540
- }
2541
- else
2542
- fprintf(stderr, "OK: strings dictionary has 2 items\n");
2543
- }
2544
-
2545
- // check 2-way merge
2546
- {
2547
- struct testcase {
2548
- char *src1; char *src2; char *expected;
2549
- } tests[] = {
2550
- { "", "", ""},
2551
- { "a", "", "[x]"},
2552
- { "", "a", "[x]"},
2553
- { "a", "a", "a"},
2554
- { "abcd", "abcd", "abcd"},
2555
- { "foo_cs", "bar_cs", "[x]_cs"},
2556
- { "cp_UNIQUE_INFIX_cs", "cp_unique_infix_cs", "cp_[x]_cs"},
2557
- { "cp_UNIQUE_INFIX_ci_unique_infix_cs", "cp_unique_infix_ci_UNIQUE_INFIX_cs", "cp_[x]_cs"},
2558
- { "foo[1234]", "foo[4321]", "foo[[x]]"},
2559
- { NULL, NULL, NULL },
2560
- };
2561
-
2562
- for (struct testcase *tc = &tests[0]; tc->expected != NULL; tc++) {
2563
- STRING *src1 = string_strdupz(tc->src1);
2564
- STRING *src2 = string_strdupz(tc->src2);
2565
- STRING *expected = string_strdupz(tc->expected);
2566
-
2567
- STRING *result = string_2way_merge(src1, src2);
2568
- if (string_cmp(result, expected) != 0) {
2569
- fprintf(stderr, "string_2way_merge(\"%s\", \"%s\") -> \"%s\" (expected=\"%s\")\n",
2570
- string2str(src1),
2571
- string2str(src2),
2572
- string2str(result),
2573
- string2str(expected));
2574
- errors++;
2575
- }
2576
-
2577
- string_freez(src1);
2578
- string_freez(src2);
2579
- string_freez(expected);
2580
- string_freez(result);
2581
- }
2582
- }
2583
-
3302
dictionary_unittest_free_char_pp(names, entries);
3303
dictionary_unittest_free_char_pp(values, entries);
3304
2587
- {
2588
-#ifdef NETDATA_INTERNAL_CHECKS
2589
- size_t ofound_deleted_on_search = string_base.found_deleted_on_search,
2590
- ofound_available_on_search = string_base.found_available_on_search,
2591
- ofound_deleted_on_insert = string_base.found_deleted_on_insert,
2592
- ofound_available_on_insert = string_base.found_available_on_insert,
2593
- ospins = string_base.spins;
2594
-#endif
2595
-
2596
- size_t oinserts, odeletes, osearches, oentries, oreferences, omemory, oduplications, oreleases;
2597
- string_statistics(&oinserts, &odeletes, &osearches, &oentries, &oreferences, &omemory, &oduplications, &oreleases);
2598
-
2599
- time_t seconds_to_run = 5;
2600
- int threads_to_create = 2;
2601
- fprintf(
2602
- stderr,
2603
- "Checking string concurrency with %d threads for %ld seconds...\n",
2604
- threads_to_create,
2605
- seconds_to_run);
2606
- // check string concurrency
2607
- netdata_thread_t threads[threads_to_create];
2608
- string_threads_join = 0;
2609
- for (int i = 0; i < threads_to_create; i++) {
2610
- char buf[100 + 1];
2611
- snprintf(buf, 100, "string%d", i);
2612
- netdata_thread_create(
2613
- &threads[i], buf, NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE, string_thread, NULL);
2614
- }
2615
- sleep_usec(seconds_to_run * USEC_PER_SEC);
2616
-
2617
- string_threads_join = 1;
2618
- for (int i = 0; i < threads_to_create; i++) {
2619
- void *retval;
2620
- netdata_thread_join(threads[i], &retval);
2621
- }
2622
-
2623
- size_t inserts, deletes, searches, sentries, references, memory, duplications, releases;
2624
- string_statistics(&inserts, &deletes, &searches, &sentries, &references, &memory, &duplications, &releases);
2625
-
2626
- fprintf(stderr, "inserts %zu, deletes %zu, searches %zu, entries %zu, references %zu, memory %zu, duplications %zu, releases %zu\n",
2627
- inserts - oinserts, deletes - odeletes, searches - osearches, sentries - oentries, references - oreferences, memory - omemory, duplications - oduplications, releases - oreleases);
2628
-
2629
-#ifdef NETDATA_INTERNAL_CHECKS
2630
- size_t found_deleted_on_search = string_base.found_deleted_on_search,
2631
- found_available_on_search = string_base.found_available_on_search,
2632
- found_deleted_on_insert = string_base.found_deleted_on_insert,
2633
- found_available_on_insert = string_base.found_available_on_insert,
2634
- spins = string_base.spins;
2635
-
2636
- fprintf(stderr, "on insert: %zu ok + %zu deleted\non search: %zu ok + %zu deleted\nspins: %zu\n",
2637
- found_available_on_insert - ofound_available_on_insert,
2638
- found_deleted_on_insert - ofound_deleted_on_insert,
2639
- found_available_on_search - ofound_available_on_search,
2640
- found_deleted_on_search - ofound_deleted_on_search,
2641
- spins - ospins
2642
- );
2643
-#endif
2644
- }
3305
+ errors += dictionary_unittest_views();
3306
+ errors += dictionary_unittest_threads();
3307
+ errors += dictionary_unittest_view_threads();
3308
3309
fprintf(stderr, "\n%zu errors found\n", errors);
3310
return errors ? 1 : 0;