@cryptotaxi247 / netdata-1 / commits / aa3be2f06

Dictionaries with reference counters and full deletion support during traversal (#13195)

* dont use atomic operations when not needed; detect misuse of the the unsafe functions * use relaxed atomic operations for statistics * use relaxed atomic operations for statistics * dictionaries now use reference counters, allowing deletetions of any item while traversing it * added acquire/release interface to dictionaries * added unittest for reference counters * added NETDATA_INTERNAL_CHECKS logs to detect non-exclusive access to crusial parts of the dictionaries * dictionaries cannot be deleted while there are referenced items in them - they will be deleted once the last item gets unreferenced * cleanup * properly cleanup released items * maintain counters for readers and writers; defer all deletes on sorted walkthrough; cleaner internal_error(); * somewhat faster reference counters on single threaded dictionaries * minor optimizations; allow compiling without internal checks

Costa Tsaousis committed Jun 28, 2022 at 17:48 UTC aa3be2f0647ace385e5ffb475a3e145f38458e6e
8 files changed +844 -270
collectors/statsd.plugin/statsd.c
+1 -1
@@ -390,7 +390,7 @@ static void dictionary_metric_insert_callback(const char *name, void *value, voi
390 netdata_mutex_init(&m->histogram.ext->mutex);
391 }
392
393 - __atomic_fetch_add(&index->metrics, 1, __ATOMIC_SEQ_CST);
393 + __atomic_fetch_add(&index->metrics, 1, __ATOMIC_RELAXED);
394 }
395
396 static void dictionary_metric_delete_callback(const char *name, void *value, void *data) {
daemon/global_statistics.c
+28 -29
@@ -46,9 +46,9 @@ static struct global_statistics {
46 };
47
48 void rrdr_query_completed(uint64_t db_points_read, uint64_t result_points_generated) {
49 - __atomic_fetch_add(&global_statistics.rrdr_queries_made, 1, __ATOMIC_SEQ_CST);
50 - __atomic_fetch_add(&global_statistics.rrdr_db_points_read, db_points_read, __ATOMIC_SEQ_CST);
51 - __atomic_fetch_add(&global_statistics.rrdr_result_points_generated, result_points_generated, __ATOMIC_SEQ_CST);
49 + __atomic_fetch_add(&global_statistics.rrdr_queries_made, 1, __ATOMIC_RELAXED);
50 + __atomic_fetch_add(&global_statistics.rrdr_db_points_read, db_points_read, __ATOMIC_RELAXED);
51 + __atomic_fetch_add(&global_statistics.rrdr_result_points_generated, result_points_generated, __ATOMIC_RELAXED);
52 }
53
54 void finished_web_request_statistics(uint64_t dt,
@@ -58,45 +58,44 @@ void finished_web_request_statistics(uint64_t dt,
58 uint64_t compressed_content_size) {
59 uint64_t old_web_usec_max = global_statistics.web_usec_max;
60 while(dt > old_web_usec_max)
61 - __atomic_compare_exchange(&global_statistics.web_usec_max, &old_web_usec_max, &dt, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
62 -
63 - __atomic_fetch_add(&global_statistics.web_requests, 1, __ATOMIC_SEQ_CST);
64 - __atomic_fetch_add(&global_statistics.web_usec, dt, __ATOMIC_SEQ_CST);
65 - __atomic_fetch_add(&global_statistics.bytes_received, bytes_received, __ATOMIC_SEQ_CST);
66 - __atomic_fetch_add(&global_statistics.bytes_sent, bytes_sent, __ATOMIC_SEQ_CST);
67 - __atomic_fetch_add(&global_statistics.content_size, content_size, __ATOMIC_SEQ_CST);
68 - __atomic_fetch_add(&global_statistics.compressed_content_size, compressed_content_size, __ATOMIC_SEQ_CST);
61 + __atomic_compare_exchange(&global_statistics.web_usec_max, &old_web_usec_max, &dt, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
62 +
63 + __atomic_fetch_add(&global_statistics.web_requests, 1, __ATOMIC_RELAXED);
64 + __atomic_fetch_add(&global_statistics.web_usec, dt, __ATOMIC_RELAXED);
65 + __atomic_fetch_add(&global_statistics.bytes_received, bytes_received, __ATOMIC_RELAXED);
66 + __atomic_fetch_add(&global_statistics.bytes_sent, bytes_sent, __ATOMIC_RELAXED);
67 + __atomic_fetch_add(&global_statistics.content_size, content_size, __ATOMIC_RELAXED);
68 + __atomic_fetch_add(&global_statistics.compressed_content_size, compressed_content_size, __ATOMIC_RELAXED);
69 }
70
71 uint64_t web_client_connected(void) {
72 - __atomic_fetch_add(&global_statistics.connected_clients, 1, __ATOMIC_SEQ_CST);
73 - return __atomic_fetch_add(&global_statistics.web_client_count, 1, __ATOMIC_SEQ_CST);
72 + __atomic_fetch_add(&global_statistics.connected_clients, 1, __ATOMIC_RELAXED);
73 + return __atomic_fetch_add(&global_statistics.web_client_count, 1, __ATOMIC_RELAXED);
74 }
75
76 void web_client_disconnected(void) {
77 - __atomic_fetch_sub(&global_statistics.connected_clients, 1, __ATOMIC_SEQ_CST);
77 + __atomic_fetch_sub(&global_statistics.connected_clients, 1, __ATOMIC_RELAXED);
78 }
79
80
81 static inline void global_statistics_copy(struct global_statistics *gs, uint8_t options) {
82 - gs->connected_clients = __atomic_fetch_add(&global_statistics.connected_clients, 0, __ATOMIC_SEQ_CST);
83 - gs->web_requests = __atomic_fetch_add(&global_statistics.web_requests, 0, __ATOMIC_SEQ_CST);
84 - gs->web_usec = __atomic_fetch_add(&global_statistics.web_usec, 0, __ATOMIC_SEQ_CST);
85 - gs->web_usec_max = __atomic_fetch_add(&global_statistics.web_usec_max, 0, __ATOMIC_SEQ_CST);
86 - gs->bytes_received = __atomic_fetch_add(&global_statistics.bytes_received, 0, __ATOMIC_SEQ_CST);
87 - gs->bytes_sent = __atomic_fetch_add(&global_statistics.bytes_sent, 0, __ATOMIC_SEQ_CST);
88 - gs->content_size = __atomic_fetch_add(&global_statistics.content_size, 0, __ATOMIC_SEQ_CST);
89 - gs->compressed_content_size = __atomic_fetch_add(&global_statistics.compressed_content_size, 0, __ATOMIC_SEQ_CST);
90 - gs->web_client_count = __atomic_fetch_add(&global_statistics.web_client_count, 0, __ATOMIC_SEQ_CST);
91 -
92 - gs->rrdr_queries_made = __atomic_fetch_add(&global_statistics.rrdr_queries_made, 0, __ATOMIC_SEQ_CST);
93 - gs->rrdr_db_points_read = __atomic_fetch_add(&global_statistics.rrdr_db_points_read, 0, __ATOMIC_SEQ_CST);
94 - gs->rrdr_result_points_generated = __atomic_fetch_add(&global_statistics.rrdr_result_points_generated, 0, __ATOMIC_SEQ_CST);
82 + gs->connected_clients = __atomic_fetch_add(&global_statistics.connected_clients, 0, __ATOMIC_RELAXED);
83 + gs->web_requests = __atomic_fetch_add(&global_statistics.web_requests, 0, __ATOMIC_RELAXED);
84 + gs->web_usec = __atomic_fetch_add(&global_statistics.web_usec, 0, __ATOMIC_RELAXED);
85 + gs->web_usec_max = __atomic_fetch_add(&global_statistics.web_usec_max, 0, __ATOMIC_RELAXED);
86 + gs->bytes_received = __atomic_fetch_add(&global_statistics.bytes_received, 0, __ATOMIC_RELAXED);
87 + gs->bytes_sent = __atomic_fetch_add(&global_statistics.bytes_sent, 0, __ATOMIC_RELAXED);
88 + gs->content_size = __atomic_fetch_add(&global_statistics.content_size, 0, __ATOMIC_RELAXED);
89 + gs->compressed_content_size = __atomic_fetch_add(&global_statistics.compressed_content_size, 0, __ATOMIC_RELAXED);
90 + gs->web_client_count = __atomic_fetch_add(&global_statistics.web_client_count, 0, __ATOMIC_RELAXED);
91 +
92 + gs->rrdr_queries_made = __atomic_fetch_add(&global_statistics.rrdr_queries_made, 0, __ATOMIC_RELAXED);
93 + gs->rrdr_db_points_read = __atomic_fetch_add(&global_statistics.rrdr_db_points_read, 0, __ATOMIC_RELAXED);
94 + gs->rrdr_result_points_generated = __atomic_fetch_add(&global_statistics.rrdr_result_points_generated, 0, __ATOMIC_RELAXED);
95
96 if(options & GLOBAL_STATS_RESET_WEB_USEC_MAX) {
97 uint64_t n = 0;
98 - __atomic_compare_exchange(&global_statistics.web_usec_max, (uint64_t *) &gs->web_usec_max, &n, 1, __ATOMIC_SEQ_CST,
99 - __ATOMIC_SEQ_CST);
98 + __atomic_compare_exchange(&global_statistics.web_usec_max, (uint64_t *) &gs->web_usec_max, &n, 1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
99 }
100 }
101
database/rrdlabels.c
+9 -14
@@ -611,20 +611,15 @@ void rrdlabels_add_pair(DICTIONARY *dict, const char *string, RRDLABEL_SRC ls) {
611 // rrdlabels_get_to_buffer_or_null()
612
613 void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const char *key, const char *quote, const char *null) {
614 - // Get a read lock on the dictionary
615 - // to copy the value into the buffer
616 - void *v;
617 - dfe_start_read(labels, v) {
618 - RRDLABEL *lb = dictionary_get_having_read_lock(labels, key);
619 -
620 - if(lb && lb->value)
621 - buffer_sprintf(wb, "%s%s%s", quote, lb->value, quote);
622 - else
623 - buffer_strcat(wb, null);
624 -
625 - break;
626 - }
627 - dfe_done(v);
614 + void *acquired_item = dictionary_acquire_item(labels, key);
615 + RRDLABEL *lb = dictionary_acquired_item_value(labels, acquired_item);
616 +
617 + if(lb && lb->value)
618 + buffer_sprintf(wb, "%s%s%s", quote, lb->value, quote);
619 + else
620 + buffer_strcat(wb, null);
621 +
622 + dictionary_acquired_item_release(labels, acquired_item);
623 }
624
625
libnetdata/dictionary/README.md
+56 -18
@@ -22,7 +22,7 @@ Dictionaries are **ordered**, meaning that the order they have been added is pre
22
23 Dictionaries guarantee **uniqueness** of all items added to them, meaning that only one item with a given name can exist in the dictionary at any given time.
24
25 -Dictionaries are extremely fast in all operations. They are indexing the keys with `JudyHS` and they utilize a double-linked-list for the traversal operations. Deletion is the most expensive operation, usually somewhat slower than insertion.
25 +Dictionaries are extremely fast in all operations. They are indexing the keys with `JudyHS` (or `AVL` when `libJudy` is not available) and they utilize a double-linked-list for the traversal operations. Deletion is the most expensive operation, usually somewhat slower than insertion.
26
27 ## Memory management
28
@@ -31,13 +31,13 @@ Dictionaries come with 2 memory management options:
31 - **Clone** (copy) the name and/or the value to memory allocated by the dictionary.
32 - **Link** the name and/or the value, without allocating any memory about them.
33
34 -In **clone** mode, the dictionary guarantees that all operations on the dictionary items will automatically take care of the memory used by the name and/or the value. In case the value is an object needs to have user allocated memory, the following callback functions can be registered:
34 +In **clone** mode, the dictionary guarantees that all operations on the dictionary items will automatically take care of the memory used by the name and/or the value. In case the value is an object that needs to have user allocated memory, the following callback functions can be registered:
35
36 1.`dictionary_register_insert_callback()` that will be called just after the insertion of an item to the dictionary, or after the replacement of the value of a dictionary item (but while the dictionary is write-locked - if locking is enabled).
37 2. `dictionary_register_delete_callback()` that will be called just prior to the deletion of an item from the dictionary, or prior to the replacement of the value of a dictionary item (but while the dictionary is write-locked - if locking is enabled).
38 3. `dictionary_register_conflict_callback()` that will be called when `DICTIONARY_FLAG_DONT_OVERWRITE_VALUE` is set and another value is attempted to be inserted for the same key.
39
40 -In **link** mode, the name and/or the value are just linked to the dictionary item, and it is the user's responsibility to free the memory used after an item is deleted from the dictionary.
40 +In **link** mode, the name and/or the value are just linked to the dictionary item, and it is the user's responsibility to free the memory they use after an item is deleted from the dictionary or when the dictionary is destroyed.
41
42 By default, **clone** mode is used for both the name and the value.
43
@@ -63,7 +63,7 @@ The dictionary supports the following operations supported by the hash table:
63
64 Use `dictionary_create()` to create a dictionary.
65
66 -Use `dictionary_destroy()` to destroy a dictionary. When destroyed, a dictionary frees all the memory it has allocated on its own. The exception is the registration of a deletion callback function that can be called on deletion of an item, which may free additional resources.
66 +Use `dictionary_destroy()` to destroy a dictionary. When destroyed, a dictionary frees all the memory it has allocated on its own. This can be complemented by the registration of a deletion callback function that can be called upon deletion of each item in the dictionary, which may free additional resources.
67
68 ### dictionary_set()
69
@@ -72,7 +72,7 @@ This call is used to:
72 - **add** an item to the dictionary.
73 - **reset** the value of an existing item in the dictionary.
74
75 -If **resetting** is not desired, add `DICTIONARY_FLAG_DONT_OVERWRITE_VALUE` to the flags when creating the dictionary. In this case, `dictionary_set()` will return the value of the original item found in the dictionary instead of resetting it and the value passed to the call will be ignored.
75 +If **resetting** is not desired, add `DICTIONARY_FLAG_DONT_OVERWRITE_VALUE` to the flags when creating the dictionary. In this case, `dictionary_set()` will return the value of the original item found in the dictionary instead of resetting it and the value passed to the call will be ignored. Optionally a conflict callback function can be registered, to manipulate (probably merge or extend) the original value, based on the new value attempted to be added to the dictionary.
76
77 For **multi-threaded** operation, the `dictionary_set()` calls get an exclusive write lock on the dictionary.
78
@@ -89,14 +89,16 @@ Where:
89 * `value` is a pointer to the value associated with this item. In **clone** mode, if `value` is `NULL`, a new memory allocation will be made of `value_len` size and will be initialized to zero.
90 * `value_len` is the size of the `value` data. If `value_len` is zero, no allocation will be done and the dictionary item will permanently have the `NULL` value.
91
92 -> **IMPORTANT**<br/>There is also an **unsafe** version (without locks) of this call. This is to be used when traversing the dictionary. It should never be called without an active lock on the dictionary, which can only be acquired while traversing.
92 +> **IMPORTANT**<br/>There is also an **unsafe** version (without locks) of this call. This is to be used when traversing the dictionary in write mode. It should never be called without an active lock on the dictionary, which can only be acquired while traversing.
93
94 ### dictionary_get()
95
96 -This call is used to get the value of an item, given its name. It utilizes the JudyHS hash table for making the lookup.
96 +This call is used to get the value of an item, given its name. It utilizes the `JudyHS` hash table for making the lookup.
97
98 For **multi-threaded** operation, the `dictionary_get()` call gets a shared read lock on the dictionary.
99
100 +In clone mode, the value returned is not guaranteed to be valid, as any other thread may delete the item from the dictionary at any time. To ensure the value will be available, use `dictionary_acquire_item()`, which uses a reference counter to defer deletes until the item is released.
101 +
102 The format is:
103
104 ```c
@@ -114,7 +116,7 @@ Where:
116
117 This call is used to delete an item from the dictionary, given its name.
118
117 -If there is a delete callback registered to the dictionary (`dictionary_register_delete_callback()`), it is called prior to the actual deletion of the item.
119 +If there is a deletion callback registered to the dictionary (`dictionary_register_delete_callback()`), it is called prior to the actual deletion of the item.
120
121 For **multi-threaded** operation, the `dictionary_del()` calls get an exclusive write lock on the dictionary.
122
@@ -131,29 +133,65 @@ Where:
133
134 > **IMPORTANT**<br/>There is also an **unsafe** version (without locks) of this call. This is to be used when traversing the dictionary, to delete the current item. It should never be called without an active lock on the dictionary, which can only be acquired while traversing.
135
136 +### dictionary_acquire_item()
137 +
138 +This call can be used the search and get a dictionary item, while ensuring that it will be available for use, until `dictionary_acquired_item_release()` is called.
139 +
140 +This call **does not return the value** of the dictionary item. It returns an internal pointer to a structure that maintains the reference counter used to protect the actual value. To get the value of the item (the same value as returned by `dictionary_get()`), the function `dictionary_acquired_item_value()` has to be called.
141 +
142 +Example:
143 +
144 +```c
145 +// create the dictionary
146 +DICTIONARY *dict = dictionary_create(DICTIONARY_FLAGS_NONE);
147 +
148 +// add an item to it
149 +dictionary_set(dict, "name", "value", 6);
150 +
151 +// find the item we added and acquire it
152 +void *item = dictionary_acquire_item(dict, "name");
153 +
154 +// extract its value
155 +char *value = (char *)dictionary_acquired_item_value(dict, item);
156 +
157 +// now value points to the string "value"
158 +printf("I got value = '%s'\n", value);
159 +
160 +// release the item, so that it can deleted
161 +dictionary_acquired_item_release(dict, item);
162 +
163 +// destroy the dictionary
164 +dictionary_destroy(dict);
165 +```
166 +
167 +When items are acquired, a reference counter is maintained to keep track of how many users exist for it. If an item with a non-zero number of users is deleted, it is removed from the index, it can be added again to the index (without conflict), and although it exists in the linked-list, it is not offered during traversal. Garbage collection to actually delete the item happens every time a write-locked dictionary is unlocked (just before the unlock) and items are deleted only if no users are using them.
168 +
169 +If any item is still acquired when the dictionary is destroyed, the destruction of the dictionary is also deferred until all the acquired items are released. When the dictionary is destroyed like that, all operations on the dictionary fail (traversals do not traverse, insertions do not insert, deletions do not delete, searches do not find any items, etc). Once the last item in the dictionary is released, the dictionary is automatically destroyed too.
170 +
171 ## Traversal
172
136 -Dictionaries offer 2 ways to traverse the entire dictionary:
173 +Dictionaries offer 3 ways to traverse the entire dictionary:
174
175 - **walkthrough**, implemented by setting a callback function to be called for every item.
176 +- **sorted walkthrough**, which first sorts the dictionary and then call a callback function for every item.
177 - **foreach**, a way to traverse the dictionary with a for-next loop.
178
141 -Both of these methods are available in **read** or **write** mode. In **read** mode only lookups are allowed to the dictionary. In **write** both lookups but also deletion of the currently working item is also allowed.
179 +All these methods are available in **read** or **write** mode. In **read** mode only lookups are allowed to the dictionary. In **write** lookups but also insertions and deletions are allowed.
180
143 -While traversing the dictionary with any of these methods, all calls to the dictionary have to use the `_unsafe` versions of the function calls, otherwise deadlock may arise.
181 +While traversing the dictionary with any of these methods, all calls to the dictionary have to use the `_unsafe` versions of the function calls, otherwise deadlocks may arise.
182
183 > **IMPORTANT**<br/>The dictionary itself does not check to ensure that a user is actually using the right lock mode (read or write) while traversing the dictionary for each of the unsafe calls.
184
185 ### walkthrough (callback)
186
149 -There are 2 calls:
187 +There are 4 calls:
188
151 -- `dictionary_walkthrough_read()` that acquires a shared read lock, and it calls a callback function for every item of the dictionary. The callback function may use the unsafe versions of the `dictionary_get()` calls to lookup other items in the dictionary, but it should not add or remove item from the dictionary.
152 -- `dictionary_walkthrough_write()` that acquires an exclusive write lock, and it calls a callback function for every item of the dictionary. This is to be used when items need to be added to the dictionary, or when the current item may need to be deleted. If the callback function deletes any other items, the behavior may be undefined (actually, the item next to the one currently working should not be deleted - a pointer to it is held by the traversal function to move on traversing the dictionary).
189 +- `dictionary_walkthrough_read()` and `dictionary_sorted_walkthrough_read()` that acquire a shared read lock, and they call a callback function for every item of the dictionary. The callback function may use the unsafe versions of the `dictionary_get()` calls to lookup other items in the dictionary, but it should not attempt to add or remove items to/from the dictionary.
190 +- `dictionary_walkthrough_write()` and `dictionary_sorted_walkthrough_write()` that acquire an exclusive write lock, and they call a callback function for every item of the dictionary. This is to be used when items need to be added to or removed from the dictionary. The `write` versions can be used to delete any or all the items from the dictionary, including the currently working one. For the `sorted` version, all items in the dictionary maintain a reference counter, so all deletions are deferred until the sorted walkthrough finishes.**
191
154 -The items are traversed in the same order they have been added to the dictionary (or the reverse order if the flag `DICTIONARY_FLAG_ADD_IN_FRONT` is set during dictionary creation).
192 +The non sorted versions traverse the items in the same order they have been added to the dictionary (or the reverse order if the flag `DICTIONARY_FLAG_ADD_IN_FRONT` is set during dictionary creation). The sorted versions sort alphabetically the items based on their name, and then they traverse them in the sorted order.
193
156 -The callback function returns an `int`. If this value is negative, traversal of the dictionary is stopped immediately and the negative value is returned to the caller. If the returned value of all callbacks is zero or positive, the walkthrough functions return the sum of the return values of all callbacks. So, if you are just interested to know how many items fall into some condition, write a callback function that returns 1 when the item satisfies that condition and 0 when it does not and the walkthrough function will return how many tested positive.
194 +The callback function returns an `int`. If this value is negative, traversal of the dictionary is stopped immediately and the negative value is returned to the caller. If the returned value of all callback calls is zero or positive, the walkthrough functions return the sum of the return values of all callbacks. So, if you are just interested to know how many items fall into some condition, write a callback function that returns 1 when the item satisfies that condition and 0 when it does not and the walkthrough function will return how many tested positive.
195
196 ### foreach (for-next loop)
197
@@ -186,14 +224,14 @@ else
224 something else;
225 ```
226
189 -In the above, the `if(x)` condition will work as expected. It will do the foreach loop when x is 1, otherwise it will run `something else`.
227 +In the above, the `if(x == 1)` condition will work as expected. It will do the foreach loop when x is 1, otherwise it will run `something else`.
228
229 There are 2 versions of `dfe_start`:
230
231 - `dfe_start_read()` that acquires a shared read lock to the dictionary.
232 - `dfe_start_write()` that acquires an exclusive write lock to the dictionary.
233
196 -While in the loop, depending on the read or write versions of `dfe_start`, the caller may lookup or manipulate the dictionary. The rules are the same with the walkthrough callback functions.
234 +While in the loop, depending on the read or write versions of `dfe_start`, the caller may lookup or manipulate the dictionary using the unsafe functions. The rules are the same with the unsorted walkthrough callback functions.
235
236 PS: DFE is Dictionary For Each.
237
libnetdata/dictionary/dictionary.c
+716 -190
@@ -1,7 +1,12 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -// NOT TO BE USED BY USERS YET
4 -#define DICTIONARY_FLAG_REFERENCE_COUNTERS (1 << 5) // maintain reference counter in walkthrough and foreach
3 +// NOT TO BE USED BY USERS
4 +#define DICTIONARY_FLAG_EXCLUSIVE_ACCESS (1 << 29) // there is only one thread accessing the dictionary
5 +#define DICTIONARY_FLAG_DESTROYED (1 << 30) // this dictionary has been destroyed
6 +#define DICTIONARY_FLAG_DEFER_ALL_DELETIONS (1 << 31) // 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
11 typedef struct dictionary DICTIONARY;
12 #define DICTIONARY_INTERNALS
@@ -19,56 +24,15 @@ typedef struct dictionary DICTIONARY;
24 #include <Judy.h>
25 #endif
26
22 -/*
23 - * This version uses JudyHS arrays to index the dictionary
24 - *
25 - * The following output is from the unit test, at the end of this file:
26 - *
27 - * This is the JudyHS version:
28 - *
29 - * 1000000 x dictionary_set() (dictionary size 0 entries, 0 KB)...
30 - * 1000000 x dictionary_get(existing) (dictionary size 1000000 entries, 74001 KB)...
31 - * 1000000 x dictionary_get(non-existing) (dictionary size 1000000 entries, 74001 KB)...
32 - * Walking through the dictionary (dictionary size 1000000 entries, 74001 KB)...
33 - * 1000000 x dictionary_del(existing) (dictionary size 1000000 entries, 74001 KB)...
34 - * 1000000 x dictionary_set() (dictionary size 0 entries, 0 KB)...
35 - * Destroying dictionary (dictionary size 1000000 entries, 74001 KB)...
36 - *
37 - * TIMINGS:
38 - * adding 316027 usec, positive search 156740 usec, negative search 84524, walk through 15036 usec, deleting 361444, destroy 107394 usec
39 - *
40 - * This is from the JudySL version:
41 - *
42 - * Creating dictionary of 1000000 entries...
43 - * Checking index of 1000000 entries...
44 - * Walking 1000000 entries and checking name-value pairs...
45 - * Created and checked 1000000 entries, found 0 errors - used 58376 KB of memory
46 - * Destroying dictionary of 1000000 entries...
47 - * Deleted 1000000 entries
48 - * create 338975 usec, check 156080 usec, walk 80764 usec, destroy 444569 usec
49 - *
50 - * This is the AVL version:
51 - *
52 - * Creating dictionary of 1000000 entries...
53 - * Checking index of 1000000 entries...
54 - * Walking 1000000 entries and checking name-value pairs...
55 - * Created and checked 1000000 entries, found 0 errors - used 89626 KB of memory
56 - * Destroying dictionary of 1000000 entries...
57 - * create 413892 usec, check 220006 usec, walk 34247 usec, destroy 98062 usec
58 - *
59 - * So, the JudySL is a lot slower to WALK and DESTROY (DESTROY does a WALK)
60 - * It is slower, because for every item, JudySL copies the KEY/NAME to a
61 - * caller supplied buffer (Index). So, by just walking over 1 million items,
62 - * JudySL does 1 million strcpy() !!!
63 - *
64 - * It also seems that somehow JudySLDel() is unbelievably slow too!
65 - *
66 - */
67 -
27 +typedef enum name_value_flags {
28 + NAME_VALUE_FLAG_NONE = 0,
29 + NAME_VALUE_FLAG_DELETED = (1 << 0), // this item is deleted
30 +} NAME_VALUE_FLAGS;
31
32 /*
33 * Every item in the dictionary has the following structure.
34 */
35 +
36 typedef struct name_value {
37 #ifdef DICTIONARY_WITH_AVL
38 avl_t avl_node;
@@ -83,26 +47,10 @@ typedef struct name_value {
47 void *value; // the value of the dictionary item
48 char *name; // the name of the dictionary item
49
50 + int refcount; // the reference counter
51 + NAME_VALUE_FLAGS flags; // the flags for this item
52 } NAME_VALUE;
53
88 -/*
89 - * When DICTIONARY_FLAG_REFERENCE_COUNTERS is set, we need to keep track of all the memory
90 - * we allocate and free. So, we need to keep track of the sizes of all names and values.
91 - * We do this by overloading NAME_VALUE with the following additional fields.
92 - */
93 -
94 -typedef enum name_value_flags {
95 - NAME_VALUE_FLAG_NONE = 0,
96 - NAME_VALUE_FLAG_DELETED = (1 << 0), // this item is deleted
97 -} NAME_VALUE_FLAGS;
98 -
99 -typedef struct name_value_with_reference_counters {
100 - NAME_VALUE name_value_data_here; // never used - just to put the lengths at the right position
101 -
102 - size_t refcount; // the reference counter
103 - NAME_VALUE_FLAGS flags; // the flags for this item
104 -} NAME_VALUE_WITH_REFERENCE_COUNTERS;
105 -
54 struct dictionary {
55 DICTIONARY_FLAGS flags; // the flags of the dictionary
56
@@ -129,15 +77,25 @@ struct dictionary {
77 void (*conflict_callback)(const char *name, void *old_value, void *new_value, void *data);
78 void *conflict_callback_data;
79
132 - size_t inserts;
133 - size_t deletes;
134 - size_t searches;
135 - size_t resets;
136 - size_t entries;
137 - size_t walkthroughs;
138 - size_t memory;
80 + size_t inserts; // how many index insertions have been performed
81 + size_t deletes; // how many index deletions have been performed
82 + size_t searches; // how many index searches have been performed
83 + size_t resets; // how many times items have reset their values
84 + size_t walkthroughs; // how many walkthroughs have been done
85 + long int memory; // how much memory the dictionary has currently allocated
86 + long int entries; // how many items are currently in the index (the linked list may have more)
87 + long int referenced_items; // how many items of the dictionary are currently being used by 3rd parties
88 + long int pending_deletion_items; // how many items of the dictionary have been deleted, but have not been removed yet
89 + int readers; // how many readers are currently using the dictionary
90 + int writers; // how many writers are currently using the dictionary
91 };
92
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 +
96 +// ----------------------------------------------------------------------------
97 +// callbacks registration
98 +
99 void dictionary_register_insert_callback(DICTIONARY *dict, void (*ins_callback)(const char *name, void *value, void *data), void *data) {
100 dict->ins_callback = ins_callback;
101 dict->ins_callback_data = data;
@@ -156,10 +114,10 @@ void dictionary_register_conflict_callback(DICTIONARY *dict, void (*conflict_cal
114 // ----------------------------------------------------------------------------
115 // dictionary statistics maintenance
116
159 -size_t dictionary_stats_allocated_memory(DICTIONARY *dict) {
117 +long int dictionary_stats_allocated_memory(DICTIONARY *dict) {
118 return dict->memory;
119 }
162 -size_t dictionary_stats_entries(DICTIONARY *dict) {
120 +long int dictionary_stats_entries(DICTIONARY *dict) {
121 return dict->entries;
122 }
123 size_t dictionary_stats_searches(DICTIONARY *dict) {
@@ -179,26 +137,114 @@ size_t dictionary_stats_walkthroughs(DICTIONARY *dict) {
137 }
138
139 static inline void DICTIONARY_STATS_SEARCHES_PLUS1(DICTIONARY *dict) {
182 - __atomic_fetch_add(&dict->searches, 1, __ATOMIC_SEQ_CST);
140 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
141 + dict->searches++;
142 + }
143 + else {
144 + __atomic_fetch_add(&dict->searches, 1, __ATOMIC_RELAXED);
145 + }
146 }
147 static inline void DICTIONARY_STATS_ENTRIES_PLUS1(DICTIONARY *dict, size_t size) {
185 - __atomic_fetch_add(&dict->inserts, 1, __ATOMIC_SEQ_CST);
186 - __atomic_fetch_add(&dict->entries, 1, __ATOMIC_SEQ_CST);
187 - __atomic_fetch_add(&dict->memory, size, __ATOMIC_SEQ_CST);
148 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
149 + dict->inserts++;
150 + dict->entries++;
151 + dict->memory += (long)size;
152 + }
153 + else {
154 + __atomic_fetch_add(&dict->inserts, 1, __ATOMIC_RELAXED);
155 + __atomic_fetch_add(&dict->entries, 1, __ATOMIC_RELAXED);
156 + __atomic_fetch_add(&dict->memory, (long)size, __ATOMIC_RELAXED);
157 + }
158 +}
159 +static inline void DICTIONARY_STATS_ENTRIES_MINUS1(DICTIONARY *dict) {
160 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
161 + dict->deletes++;
162 + dict->entries--;
163 + }
164 + else {
165 + __atomic_fetch_add(&dict->deletes, 1, __ATOMIC_RELAXED);
166 + __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_RELAXED);
167 + }
168 }
189 -static inline void DICTIONARY_STATS_ENTRIES_MINUS1(DICTIONARY *dict, size_t size) {
190 - __atomic_fetch_add(&dict->deletes, 1, __ATOMIC_SEQ_CST);
191 - __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_SEQ_CST);
192 - __atomic_fetch_sub(&dict->memory, size, __ATOMIC_SEQ_CST);
169 +static inline void DICTIONARY_STATS_ENTRIES_MINUS_MEMORY(DICTIONARY *dict, size_t size) {
170 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
171 + dict->memory -= (long)size;
172 + }
173 + else {
174 + __atomic_fetch_sub(&dict->memory, (long)size, __ATOMIC_RELAXED);
175 + }
176 }
177 static inline void DICTIONARY_STATS_VALUE_RESETS_PLUS1(DICTIONARY *dict, size_t oldsize, size_t newsize) {
195 - __atomic_fetch_add(&dict->resets, 1, __ATOMIC_SEQ_CST);
196 - __atomic_fetch_add(&dict->memory, newsize, __ATOMIC_SEQ_CST);
197 - __atomic_fetch_sub(&dict->memory, oldsize, __ATOMIC_SEQ_CST);
178 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
179 + dict->resets++;
180 + dict->memory += (long)newsize;
181 + dict->memory -= (long)oldsize;
182 + }
183 + else {
184 + __atomic_fetch_add(&dict->resets, 1, __ATOMIC_RELAXED);
185 + __atomic_fetch_add(&dict->memory, (long)newsize, __ATOMIC_RELAXED);
186 + __atomic_fetch_sub(&dict->memory, (long)oldsize, __ATOMIC_RELAXED);
187 + }
188 }
189
190 static inline void DICTIONARY_STATS_WALKTHROUGHS_PLUS1(DICTIONARY *dict) {
201 - __atomic_fetch_add(&dict->walkthroughs, 1, __ATOMIC_SEQ_CST);
191 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
192 + dict->walkthroughs++;
193 + }
194 + else {
195 + __atomic_fetch_add(&dict->walkthroughs, 1, __ATOMIC_RELAXED);
196 + }
197 +}
198 +
199 +static inline size_t DICTIONARY_STATS_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) {
200 + return __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
201 +}
202 +
203 +static inline size_t DICTIONARY_STATS_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) {
204 + return __atomic_sub_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
205 +}
206 +
207 +static inline size_t DICTIONARY_STATS_PENDING_DELETES_PLUS1(DICTIONARY *dict) {
208 + return __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST);
209 +}
210 +
211 +static inline size_t DICTIONARY_STATS_PENDING_DELETES_MINUS1(DICTIONARY *dict) {
212 + return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST);
213 +}
214 +
215 +static inline size_t DICTIONARY_STATS_PENDING_DELETES_GET(DICTIONARY *dict) {
216 + return __atomic_load_n(&dict->pending_deletion_items, __ATOMIC_SEQ_CST);
217 +}
218 +
219 +static inline int DICTIONARY_NAME_VALUE_REFCOUNT_GET(NAME_VALUE *nv) {
220 + return __atomic_load_n(&nv->refcount, __ATOMIC_SEQ_CST);
221 +}
222 +
223 +// ----------------------------------------------------------------------------
224 +// garbage collector
225 +// it is called every time someone gets a write lock to the dictionary
226 +
227 +static void garbage_collect_pending_deletes_unsafe(DICTIONARY *dict) {
228 + if(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS)) return;
229 +
230 + if(likely(!DICTIONARY_STATS_PENDING_DELETES_GET(dict))) return;
231 +
232 + NAME_VALUE *nv = dict->first_item;
233 + while(nv) {
234 + if(nv->flags & NAME_VALUE_FLAG_DELETED && DICTIONARY_NAME_VALUE_REFCOUNT_GET(nv) == 0) {
235 + NAME_VALUE *nv_next = nv->next;
236 +
237 + linkedlist_namevalue_unlink_unsafe(dict, nv);
238 + namevalue_destroy_unsafe(dict, nv);
239 +
240 + size_t pending = DICTIONARY_STATS_PENDING_DELETES_MINUS1(dict);
241 + if(!pending) break;
242 +
243 + nv = nv_next;
244 + }
245 + else
246 + nv = nv->next;
247 + }
248 }
249
250 // ----------------------------------------------------------------------------
@@ -208,8 +254,15 @@ static inline size_t dictionary_lock_init(DICTIONARY *dict) {
254 if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
255 dict->rwlock = mallocz(sizeof(netdata_rwlock_t));
256 netdata_rwlock_init(dict->rwlock);
257 +
258 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS)
259 + dict->flags &= ~DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
260 +
261 return sizeof(netdata_rwlock_t);
262 }
263 +
264 + // we are single threaded
265 + dict->flags |= DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
266 dict->rwlock = NULL;
267 return 0;
268 }
@@ -223,24 +276,79 @@ static inline size_t dictionary_lock_free(DICTIONARY *dict) {
276 return 0;
277 }
278
226 -static inline void dictionary_lock_rlock(DICTIONARY *dict) {
227 - if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
228 - // debug(D_DICTIONARY, "Dictionary READ lock");
279 +static void dictionary_lock(DICTIONARY *dict, char rw) {
280 + if(rw == 'r' || rw == 'R') {
281 + // read lock
282 + __atomic_add_fetch(&dict->readers, 1, __ATOMIC_RELAXED);
283 + }
284 + else {
285 + // write lock
286 + __atomic_add_fetch(&dict->writers, 1, __ATOMIC_RELAXED);
287 + }
288 +
289 + if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
290 + return;
291 +
292 + if(rw == 'r' || rw == 'R') {
293 + // read lock
294 netdata_rwlock_rdlock(dict->rwlock);
295 +
296 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS) {
297 + internal_error(true, "DICTIONARY: left-over exclusive access to dictionary found");
298 + dict->flags &= ~DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
299 + }
300 + }
301 + else {
302 + // write lock
303 + netdata_rwlock_wrlock(dict->rwlock);
304 +
305 + dict->flags |= DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
306 }
307 }
308
233 -static inline void dictionary_lock_wrlock(DICTIONARY *dict) {
234 - if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
235 - // debug(D_DICTIONARY, "Dictionary WRITE lock");
236 - netdata_rwlock_wrlock(dict->rwlock);
309 +static void dictionary_unlock(DICTIONARY *dict, char rw) {
310 + if(rw == 'r' || rw == 'R') {
311 + // read unlock
312 + __atomic_sub_fetch(&dict->readers, 1, __ATOMIC_RELAXED);
313 + }
314 + else {
315 + // write unlock
316 + garbage_collect_pending_deletes_unsafe(dict);
317 + __atomic_sub_fetch(&dict->writers, 1, __ATOMIC_RELAXED);
318 + }
319 +
320 + if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
321 + return;
322 +
323 + if(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS)
324 + dict->flags &= ~DICTIONARY_FLAG_EXCLUSIVE_ACCESS;
325 +
326 + netdata_rwlock_unlock(dict->rwlock);
327 +}
328 +
329 +// ----------------------------------------------------------------------------
330 +// deferred deletions
331 +
332 +void dictionary_defer_all_deletions_unsafe(DICTIONARY *dict, char rw) {
333 + if(rw == 'r' || rw == 'R') {
334 + // read locked - no need to defer deletions
335 + ;
336 + }
337 + else {
338 + // write locked - defer deletions
339 + dict->flags |= DICTIONARY_FLAG_DEFER_ALL_DELETIONS;
340 }
341 }
342
240 -static inline void dictionary_unlock(DICTIONARY *dict) {
241 - if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
242 - // debug(D_DICTIONARY, "Dictionary UNLOCK lock");
243 - netdata_rwlock_unlock(dict->rwlock);
343 +void dictionary_restore_all_deletions_unsafe(DICTIONARY *dict, char rw) {
344 + if(rw == 'r' || rw == 'R') {
345 + // read locked - no need to defer deletions
346 + internal_error(dict->flags & DICTIONARY_FLAG_DEFER_ALL_DELETIONS, "DICTIONARY: deletions are deferred on a read lock");
347 + }
348 + else {
349 + // write locked - defer deletions
350 + if(dict->flags & DICTIONARY_FLAG_DEFER_ALL_DELETIONS)
351 + dict->flags &= ~DICTIONARY_FLAG_DEFER_ALL_DELETIONS;
352 }
353 }
354
@@ -263,27 +371,56 @@ static inline size_t reference_counter_free(DICTIONARY *dict) {
371 return 0;
372 }
373
266 -static void reference_counter_acquire(DICTIONARY *dict, NAME_VALUE *nv) {
267 - if(unlikely(dict->flags & DICTIONARY_FLAG_REFERENCE_COUNTERS)) {
268 - NAME_VALUE_WITH_REFERENCE_COUNTERS *nvs = (NAME_VALUE_WITH_REFERENCE_COUNTERS *)nv;
269 - __atomic_fetch_add(&nvs->refcount, 1, __ATOMIC_SEQ_CST);
374 +static int reference_counter_acquire(DICTIONARY *dict, NAME_VALUE *nv) {
375 + int refcount;
376 + if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
377 + refcount = ++nv->refcount;
378 + else
379 + refcount = __atomic_add_fetch(&nv->refcount, 1, __ATOMIC_SEQ_CST);
380 +
381 + if(refcount == 1) {
382 + // referenced items counts number of unique items referenced
383 + // so, we increase it only when refcount == 1
384 + DICTIONARY_STATS_REFERENCED_ITEMS_PLUS1(dict);
385 +
386 + // if this is a deleted item, but the counter increased to 1
387 + // we need to remove it from the pending items to delete
388 + if (nv->flags & NAME_VALUE_FLAG_DELETED)
389 + DICTIONARY_STATS_PENDING_DELETES_MINUS1(dict);
390 }
391 +
392 + return refcount;
393 }
394
273 -static void reference_counter_release(DICTIONARY *dict, NAME_VALUE *nv) {
274 - if(unlikely(dict->flags & DICTIONARY_FLAG_REFERENCE_COUNTERS)) {
275 - NAME_VALUE_WITH_REFERENCE_COUNTERS *nvs = (NAME_VALUE_WITH_REFERENCE_COUNTERS *)nv;
276 - __atomic_fetch_sub(&nvs->refcount, 1, __ATOMIC_SEQ_CST);
395 +static int reference_counter_release(DICTIONARY *dict, NAME_VALUE *nv, bool can_get_write_lock) {
396 + // this function may be called without any lock on the dictionary
397 + // or even when someone else has a write lock on the dictionary
398 + // so, we cannot check for EXCLUSIVE ACCESS
399 +
400 + int refcount;
401 + if(likely(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))
402 + refcount = --nv->refcount;
403 + else
404 + refcount = __atomic_sub_fetch(&nv->refcount, 1, __ATOMIC_SEQ_CST);
405 +
406 + if(refcount == 0) {
407 + if((nv->flags & NAME_VALUE_FLAG_DELETED))
408 + DICTIONARY_STATS_PENDING_DELETES_PLUS1(dict);
409 +
410 + // referenced items counts number of unique items referenced
411 + // so, we decrease it only when refcount == 0
412 + DICTIONARY_STATS_REFERENCED_ITEMS_MINUS1(dict);
413 }
278 -}
414
280 -static int reference_counter_mark_deleted(DICTIONARY *dict, NAME_VALUE *nv) {
281 - if(unlikely(dict->flags & DICTIONARY_FLAG_REFERENCE_COUNTERS)) {
282 - NAME_VALUE_WITH_REFERENCE_COUNTERS *nvs = (NAME_VALUE_WITH_REFERENCE_COUNTERS *)nv;
283 - nvs->flags |= NAME_VALUE_FLAG_DELETED;
284 - return 1;
415 + if(can_get_write_lock && DICTIONARY_STATS_PENDING_DELETES_GET(dict)) {
416 + // we can garbage collect now
417 +
418 + dictionary_lock(dict, 'w');
419 + garbage_collect_pending_deletes_unsafe(dict);
420 + dictionary_unlock(dict, 'w');
421 }
286 - return 0;
422 +
423 + return refcount;
424 }
425
426 // ----------------------------------------------------------------------------
@@ -366,6 +503,8 @@ static size_t hashtable_destroy_unsafe(DICTIONARY *dict) {
503 }
504
505 static inline NAME_VALUE **hashtable_insert_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
506 + internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: inserting to the index without exclusive access to the dictionary.");
507 +
508 JError_t J_Error;
509 Pvoid_t *Rc = JudyHSIns(&dict->JudyHSArray, (void *)name, name_len, &J_Error);
510 if (unlikely(Rc == PJERR)) {
@@ -384,8 +523,9 @@ static inline NAME_VALUE **hashtable_insert_unsafe(DICTIONARY *dict, const char
523 }
524
525 static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, NAME_VALUE *nv) {
387 - (void)nv;
526 + internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: deleting from the index without exclusive access to the dictionary.");
527
528 + (void)nv;
529 if(unlikely(!dict->JudyHSArray)) return 0;
530
531 JError_t J_Error;
@@ -440,6 +580,8 @@ static inline void hashtable_inserted_name_value_unsafe(DICTIONARY *dict, const
580 // linked list management
581
582 static inline void linkedlist_namevalue_link_unsafe(DICTIONARY *dict, NAME_VALUE *nv) {
583 + internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: adding item to the linked-list without exclusive access to the dictionary.");
584 +
585 if (unlikely(!dict->first_item)) {
586 // we are the only ones here
587 nv->next = NULL;
@@ -467,6 +609,8 @@ static inline void linkedlist_namevalue_link_unsafe(DICTIONARY *dict, NAME_VALUE
609 }
610
611 static inline void linkedlist_namevalue_unlink_unsafe(DICTIONARY *dict, NAME_VALUE *nv) {
612 + internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: removing item from the linked-list without exclusive access to the dictionary.");
613 +
614 if(nv->next) nv->next->prev = nv->prev;
615 if(nv->prev) nv->prev->next = nv->next;
616 if(dict->first_item == nv) dict->first_item = nv->next;
@@ -476,17 +620,15 @@ static inline void linkedlist_namevalue_unlink_unsafe(DICTIONARY *dict, NAME_VAL
620 // ----------------------------------------------------------------------------
621 // NAME_VALUE methods
622
479 -static inline size_t namevalue_alloc_size(DICTIONARY *dict) {
480 - return (dict->flags & DICTIONARY_FLAG_REFERENCE_COUNTERS) ? sizeof(NAME_VALUE_WITH_REFERENCE_COUNTERS) : sizeof(NAME_VALUE);
481 -}
482 -
623 static NAME_VALUE *namevalue_create_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *value, size_t value_len) {
624 debug(D_DICTIONARY, "Creating name value entry for name '%s'.", name);
625
486 - size_t size = namevalue_alloc_size(dict);
626 + size_t size = sizeof(NAME_VALUE);
627 NAME_VALUE *nv = mallocz(size);
628 size_t allocated = size;
629
630 + nv->refcount = 0;
631 + nv->flags = NAME_VALUE_FLAG_NONE;
632 nv->name_len = name_len;
633 nv->value_len = value_len;
634
@@ -585,23 +727,32 @@ static size_t namevalue_destroy_unsafe(DICTIONARY *dict, NAME_VALUE *nv) {
727 }
728
729 freez(nv);
588 - freed += namevalue_alloc_size(dict);
730 + freed += sizeof(NAME_VALUE);
731
590 - DICTIONARY_STATS_ENTRIES_MINUS1(dict, freed);
732 + DICTIONARY_STATS_ENTRIES_MINUS_MEMORY(dict, freed);
733
734 return freed;
735 }
736
737 +// if a dictionary item can be deleted, return true, otherwise return false
738 +static bool name_value_can_be_deleted(DICTIONARY *dict, NAME_VALUE *nv) {
739 + if(unlikely(dict->flags & DICTIONARY_FLAG_DEFER_ALL_DELETIONS))
740 + return false;
741 +
742 + if(unlikely(DICTIONARY_NAME_VALUE_REFCOUNT_GET(nv) > 0))
743 + return false;
744 +
745 + return true;
746 +}
747 +
748 // ----------------------------------------------------------------------------
749 // API - dictionary management
750
751 DICTIONARY *dictionary_create(DICTIONARY_FLAGS flags) {
752 debug(D_DICTIONARY, "Creating dictionary.");
753
601 - if((flags & DICTIONARY_FLAG_REFERENCE_COUNTERS) && (flags & DICTIONARY_FLAG_SINGLE_THREADED)) {
602 - error("DICTIONARY: requested reference counters on single threaded dictionary. Not adding reference counters.");
603 - flags &= ~DICTIONARY_FLAG_REFERENCE_COUNTERS;
604 - }
754 + if(unlikely(flags & DICTIONARY_FLAGS_RESERVED))
755 + flags &= ~DICTIONARY_FLAGS_RESERVED;
756
757 DICTIONARY *dict = callocz(1, sizeof(DICTIONARY));
758 size_t allocated = sizeof(DICTIONARY);
@@ -611,7 +762,7 @@ DICTIONARY *dictionary_create(DICTIONARY_FLAGS flags) {
762
763 allocated += dictionary_lock_init(dict);
764 allocated += reference_counter_init(dict);
614 - dict->memory = allocated;
765 + dict->memory = (long)allocated;
766
767 hashtable_init_unsafe(dict);
768 return (DICTIONARY *)dict;
@@ -620,9 +771,14 @@ DICTIONARY *dictionary_create(DICTIONARY_FLAGS flags) {
771 size_t dictionary_destroy(DICTIONARY *dict) {
772 if(!dict) return 0;
773
774 + if(dict->referenced_items) {
775 + dict->flags |= DICTIONARY_FLAG_DESTROYED;
776 + return 0;
777 + }
778 +
779 debug(D_DICTIONARY, "Destroying dictionary.");
780
625 - dictionary_lock_wrlock(dict);
781 + dictionary_lock(dict, 'w');
782
783 size_t freed = 0;
784 NAME_VALUE *nv = dict->first_item;
@@ -642,7 +798,7 @@ size_t dictionary_destroy(DICTIONARY *dict) {
798 // destroy the dictionary
799 freed += hashtable_destroy_unsafe(dict);
800
645 - dictionary_unlock(dict);
801 + dictionary_unlock(dict, 'w');
802 freed += dictionary_lock_free(dict);
803 freed += reference_counter_free(dict);
804
@@ -657,10 +813,17 @@ size_t dictionary_destroy(DICTIONARY *dict) {
813
814 void *dictionary_set_unsafe(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
815 if(unlikely(!name || !*name)) {
660 - error("Attempted to dictionary_set() a dictionary item without a name");
816 + internal_error(true, "DICTIONARY: attempted to dictionary_set() a dictionary item without a name");
817 return NULL;
818 }
819
820 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
821 + internal_error(true, "DICTIONARY: attempted to dictionary_set() on a destroyed dictionary");
822 + return NULL;
823 + }
824 +
825 + internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: inserting dictionary item '%s' without exclusive access to dictionary", name);
826 +
827 size_t name_len = strlen(name) + 1; // we need the terminating null too
828
829 debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
@@ -700,15 +863,20 @@ void *dictionary_set_unsafe(DICTIONARY *dict, const char *name, void *value, siz
863 }
864
865 void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
703 - dictionary_lock_wrlock(dict);
866 + dictionary_lock(dict, 'w');
867 void *ret = dictionary_set_unsafe(dict, name, value, value_len);
705 - dictionary_unlock(dict);
868 + dictionary_unlock(dict, 'w');
869 return ret;
870 }
871
709 -void *dictionary_get_unsafe(DICTIONARY *dict, const char *name) {
872 +static NAME_VALUE *dictionary_get_name_value_unsafe(DICTIONARY *dict, const char *name) {
873 if(unlikely(!name || !*name)) {
711 - error("Attempted to dictionary_get() without a name");
874 + internal_error(true, "attempted to dictionary_get() without a name");
875 + return NULL;
876 + }
877 +
878 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
879 + internal_error(true, "DICTIONARY: attempted to dictionary_get() on a destroyed dictionary");
880 return NULL;
881 }
882
@@ -723,22 +891,78 @@ void *dictionary_get_unsafe(DICTIONARY *dict, const char *name) {
891 }
892
893 debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
894 + return nv;
895 +}
896 +
897 +void *dictionary_get_unsafe(DICTIONARY *dict, const char *name) {
898 + NAME_VALUE *nv = dictionary_get_name_value_unsafe(dict, name);
899 +
900 + if(unlikely(!nv))
901 + return NULL;
902 +
903 return nv->value;
904 }
905
906 void *dictionary_get(DICTIONARY *dict, const char *name) {
730 - dictionary_lock_rlock(dict);
907 + dictionary_lock(dict, 'r');
908 void *ret = dictionary_get_unsafe(dict, name);
732 - dictionary_unlock(dict);
909 + dictionary_unlock(dict, 'r');
910 return ret;
911 }
912
913 +void *dictionary_acquire_item_unsafe(DICTIONARY *dict, const char *name) {
914 + NAME_VALUE *nv = dictionary_get_name_value_unsafe(dict, name);
915 +
916 + if(unlikely(!nv))
917 + return NULL;
918 +
919 + reference_counter_acquire(dict, nv);
920 + return nv;
921 +}
922 +
923 +void *dictionary_acquire_item(DICTIONARY *dict, const char *name) {
924 + dictionary_lock(dict, 'r');
925 + void *ret = dictionary_acquire_item_unsafe(dict, name);
926 + dictionary_unlock(dict, 'r');
927 + return ret;
928 +}
929 +
930 +void *dictionary_acquired_item_value(DICTIONARY *dict __maybe_unused, void *item) {
931 + if(unlikely(!item)) return NULL;
932 + return ((NAME_VALUE *)item)->value;
933 +}
934 +
935 +void dictionary_acquired_item_release_unsafe(DICTIONARY *dict, void *item) {
936 + if(unlikely(!item)) return;
937 + reference_counter_release(dict, (NAME_VALUE *)item, false);
938 +}
939 +
940 +void dictionary_acquired_item_release(DICTIONARY *dict, void *item) {
941 + if(unlikely(!item)) return;
942 +
943 + // no need to get a lock here
944 + // we pass the last parameter to reference_counter_release() as true
945 + // so that the release may get a write-lock if required to clean up
946 +
947 + reference_counter_release(dict, (NAME_VALUE *)item, true);
948 +
949 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED))
950 + dictionary_destroy(dict);
951 +}
952 +
953 int dictionary_del_unsafe(DICTIONARY *dict, const char *name) {
954 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
955 + internal_error(true, "DICTIONARY: attempted to dictionary_del() on a destroyed dictionary");
956 + return -1;
957 + }
958 +
959 if(unlikely(!name || !*name)) {
738 - error("Attempted to dictionary_det() without a name");
960 + internal_error(true, "DICTIONARY: attempted to dictionary_del() without a name");
961 return -1;
962 }
963
964 + internal_error(!(dict->flags & DICTIONARY_FLAG_EXCLUSIVE_ACCESS), "DICTIONARY: INTERNAL ERROR: deleting dictionary item '%s' without exclusive access to dictionary", name);
965 +
966 size_t name_len = strlen(name) + 1; // we need the terminating null too
967
968 debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
@@ -759,19 +983,25 @@ int dictionary_del_unsafe(DICTIONARY *dict, const char *name) {
983 if(hashtable_delete_unsafe(dict, name, name_len, nv) == 0)
984 error("DICTIONARY: INTERNAL ERROR: tried to delete item with name '%s' that is not in the index", name);
985
762 - if(!reference_counter_mark_deleted(dict, nv)) {
986 + if(name_value_can_be_deleted(dict, nv)) {
987 linkedlist_namevalue_unlink_unsafe(dict, nv);
988 namevalue_destroy_unsafe(dict, nv);
989 }
990 + else
991 + nv->flags |= NAME_VALUE_FLAG_DELETED;
992 +
993 ret = 0;
994 +
995 + DICTIONARY_STATS_ENTRIES_MINUS1(dict);
996 +
997 }
998 return ret;
999 }
1000
1001 int dictionary_del(DICTIONARY *dict, const char *name) {
772 - dictionary_lock_wrlock(dict);
1002 + dictionary_lock(dict, 'w');
1003 int ret = dictionary_del_unsafe(dict, name);
774 - dictionary_unlock(dict);
1004 + dictionary_unlock(dict, 'w');
1005 return ret;
1006 }
1007
@@ -781,27 +1011,37 @@ int dictionary_del(DICTIONARY *dict, const char *name) {
1011 void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) {
1012 if(unlikely(!dfe || !dict)) return NULL;
1013
784 - DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1014 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1015 + internal_error(true, "DICTIONARY: attempted to dictionary_foreach_start_rw() on a destroyed dictionary");
1016 + dfe->last_item = NULL;
1017 + dfe->name = NULL;
1018 + dfe->value = NULL;
1019 + return NULL;
1020 + }
1021
1022 dfe->dict = dict;
1023 + dfe->rw = rw;
1024 dfe->started_ut = now_realtime_usec();
1025
789 - if(rw == 'r' || rw == 'R')
790 - dictionary_lock_rlock(dict);
791 - else
792 - dictionary_lock_wrlock(dict);
1026 + dictionary_lock(dict, dfe->rw);
1027
1028 + DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1029 +
1030 + // get the first item from the list
1031 NAME_VALUE *nv = dict->first_item;
795 - dfe->last_position_index = (void *)nv;
1032 +
1033 + // skip all the deleted items
1034 + while(nv && (nv->flags & NAME_VALUE_FLAG_DELETED))
1035 + nv = nv->next;
1036
1037 if(likely(nv)) {
798 - dfe->next_position_index = (void *)nv->next;
1038 + dfe->last_item = nv;
1039 dfe->name = nv->name;
800 - dfe->value = (void *)nv->value;
1040 + dfe->value = nv->value;
1041 reference_counter_acquire(dict, nv);
1042 }
1043 else {
804 - dfe->next_position_index = NULL;
1044 + dfe->last_item = NULL;
1045 dfe->name = NULL;
1046 dfe->value = NULL;
1047 }
@@ -812,21 +1052,36 @@ void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) {
1052 void *dictionary_foreach_next(DICTFE *dfe) {
1053 if(unlikely(!dfe || !dfe->dict)) return NULL;
1054
815 - NAME_VALUE *nv = (NAME_VALUE *)dfe->last_position_index;
816 - if(likely(nv))
817 - reference_counter_release(dfe->dict, nv);
1055 + if(unlikely(dfe->dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1056 + internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
1057 + dfe->last_item = NULL;
1058 + dfe->name = NULL;
1059 + dfe->value = NULL;
1060 + return NULL;
1061 + }
1062
819 - nv = dfe->last_position_index = dfe->next_position_index;
1063 + // the item we just did
1064 + NAME_VALUE *nv = (NAME_VALUE *)dfe->last_item;
1065
821 - if(likely(nv)) {
822 - dfe->next_position_index = (void *)nv->next;
823 - dfe->name = nv->name;
824 - dfe->value = (void *)nv->value;
1066 + // get the next item from the list
1067 + NAME_VALUE *nv_next = (nv) ? nv->next : NULL;
1068 +
1069 + // skip all the deleted items
1070 + while(nv_next && (nv_next->flags & NAME_VALUE_FLAG_DELETED))
1071 + nv_next = nv_next->next;
1072 +
1073 + // release the old, so that it can possibly be deleted
1074 + if(likely(nv))
1075 + reference_counter_release(dfe->dict, nv, false);
1076
1077 + if(likely(nv = nv_next)) {
1078 + dfe->last_item = nv;
1079 + dfe->name = nv->name;
1080 + dfe->value = nv->value;
1081 reference_counter_acquire(dfe->dict, nv);
1082 }
1083 else {
829 - dfe->next_position_index = NULL;
1084 + dfe->last_item = NULL;
1085 dfe->name = NULL;
1086 dfe->value = NULL;
1087 }
@@ -837,14 +1092,21 @@ void *dictionary_foreach_next(DICTFE *dfe) {
1092 usec_t dictionary_foreach_done(DICTFE *dfe) {
1093 if(unlikely(!dfe || !dfe->dict)) return 0;
1094
840 - NAME_VALUE *nv = (NAME_VALUE *)dfe->last_position_index;
841 - if(nv)
842 - reference_counter_release(dfe->dict, nv);
1095 + if(unlikely(dfe->dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1096 + internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
1097 + return 0;
1098 + }
1099 +
1100 + // the item we just did
1101 + NAME_VALUE *nv = (NAME_VALUE *)dfe->last_item;
1102 +
1103 + // release it, so that it can possibly be deleted
1104 + if(likely(nv))
1105 + reference_counter_release(dfe->dict, nv, false);
1106
844 - dictionary_unlock((DICTIONARY *)dfe->dict);
1107 + dictionary_unlock(dfe->dict, dfe->rw);
1108 dfe->dict = NULL;
846 - dfe->last_position_index = NULL;
847 - dfe->next_position_index = NULL;
1109 + dfe->last_item = NULL;
1110 dfe->name = NULL;
1111 dfe->value = NULL;
1112
@@ -862,23 +1124,38 @@ usec_t dictionary_foreach_done(DICTFE *dfe) {
1124 int dictionary_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const char *name, void *entry, void *data), void *data) {
1125 if(unlikely(!dict)) return 0;
1126
865 - DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1127 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1128 + internal_error(true, "DICTIONARY: attempted to dictionary_walkthrough_rw() on a destroyed dictionary");
1129 + return 0;
1130 + }
1131
867 - if(rw == 'r' || rw == 'R')
868 - dictionary_lock_rlock(dict);
869 - else
870 - dictionary_lock_wrlock(dict);
1132 + dictionary_lock(dict, rw);
1133 +
1134 + DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1135
1136 // written in such a way, that the callback can delete the active element
1137
1138 int ret = 0;
1139 NAME_VALUE *nv = dict->first_item, *nv_next;
1140 while(nv) {
877 - nv_next = nv->next;
1141
1142 + // skip the deleted items
1143 + if(unlikely(nv->flags & NAME_VALUE_FLAG_DELETED)) {
1144 + nv = nv->next;
1145 + continue;
1146 + }
1147 +
1148 + // get a reference counter, so that our item will not be deleted
1149 + // while we are using it
1150 reference_counter_acquire(dict, nv);
1151 +
1152 int r = callback(nv->name, nv->value, data);
881 - reference_counter_release(dict, nv);
1153 +
1154 + // since we have a reference counter, this item cannot be deleted
1155 + // until we release the reference counter, so the pointers are there
1156 + nv_next = nv->next;
1157 + reference_counter_release(dict, nv, false);
1158 +
1159 if(unlikely(r < 0)) {
1160 ret = r;
1161 break;
@@ -889,13 +1166,13 @@ int dictionary_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const c
1166 nv = nv_next;
1167 }
1168
892 - dictionary_unlock(dict);
1169 + dictionary_unlock(dict, rw);
1170
1171 return ret;
1172 }
1173
1174 // ----------------------------------------------------------------------------
898 -// sort
1175 +// sorted walkthrough
1176
1177 static int dictionary_sort_compar(const void *nv1, const void *nv2) {
1178 return strcmp((*(NAME_VALUE **)nv1)->name, (*(NAME_VALUE **)nv2)->name);
@@ -904,26 +1181,30 @@ static int dictionary_sort_compar(const void *nv1, const void *nv2) {
1181 int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(const char *name, void *entry, void *data), void *data) {
1182 if(unlikely(!dict || !dict->entries)) return 0;
1183
907 - DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1184 + if(unlikely(dict->flags & DICTIONARY_FLAG_DESTROYED)) {
1185 + internal_error(true, "DICTIONARY: attempted to dictionary_sorted_walkthrough_rw() on a destroyed dictionary");
1186 + return 0;
1187 + }
1188
909 - if(rw == 'r' || rw == 'R')
910 - dictionary_lock_rlock(dict);
911 - else
912 - dictionary_lock_wrlock(dict);
1189 + dictionary_lock(dict, rw);
1190 + dictionary_defer_all_deletions_unsafe(dict, rw);
1191 +
1192 + DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
1193
1194 size_t count = dict->entries;
1195 NAME_VALUE **array = mallocz(sizeof(NAME_VALUE *) * count);
1196
1197 size_t i;
1198 NAME_VALUE *nv;
919 - for(nv = dict->first_item, i = 0; nv && i < count ;nv = nv->next, i++)
920 - array[i] = nv;
1199 + for(nv = dict->first_item, i = 0; nv && i < count ;nv = nv->next) {
1200 + if(likely(!(nv->flags & NAME_VALUE_FLAG_DELETED)))
1201 + array[i++] = nv;
1202 + }
1203
922 - if(unlikely(nv))
923 - error("DICTIONARY: during sorting expected to have %zu items in dictionary, but there are more. Sorted results may be incomplete. This is internal error - dictionaries fail to maintain an accurate number of the number of entries they have.", count);
1204 + 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);
1205
1206 if(unlikely(i != count)) {
926 - error("DICTIONARY: during sorting expected to have %zu items in dictionary, but there are %zu. Sorted results may be incomplete. This is internal error - dictionaries fail to maintain an accurate number of the number of entries they have.", count, i);
1207 + 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);
1208 count = i;
1209 }
1210
@@ -931,12 +1212,21 @@ int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)(
1212
1213 int ret = 0;
1214 for(i = 0; i < count ;i++) {
934 - int r = callback((array[i])->name, (array[i])->value, data);
935 - if(r < 0) { ret = r; break; }
936 - ret += r;
1215 + nv = array[i];
1216 + if(likely(!(nv->flags & NAME_VALUE_FLAG_DELETED))) {
1217 + reference_counter_acquire(dict, nv);
1218 + int r = callback(nv->name, nv->value, data);
1219 + reference_counter_release(dict, nv, false);
1220 + if (r < 0) {
1221 + ret = r;
1222 + break;
1223 + }
1224 + ret += r;
1225 + }
1226 }
1227
939 - dictionary_unlock(dict);
1228 + dictionary_restore_all_deletions_unsafe(dict, rw);
1229 + dictionary_unlock(dict, rw);
1230 freez(array);
1231
1232 return ret;
@@ -986,8 +1276,8 @@ static size_t dictionary_unittest_set_clone(DICTIONARY *dict, char **names, char
1276 static size_t dictionary_unittest_set_null(DICTIONARY *dict, char **names, char **values, size_t entries) {
1277 (void)values;
1278 size_t errors = 0;
989 - size_t i = 0;
990 - for(; i < entries ;i++) {
1279 + long i = 0;
1280 + for(; i < (long)entries ;i++) {
1281 void *val = dictionary_set(dict, names[i], NULL, 0);
1282 if(val != NULL) { fprintf(stderr, ">>> %s() returns a non NULL value\n", __FUNCTION__); errors++; }
1283 }
@@ -1198,7 +1488,7 @@ static size_t dictionary_unittest_destroy(DICTIONARY *dict, char **names, char *
1488 }
1489
1490 static usec_t dictionary_unittest_run_and_measure_time(DICTIONARY *dict, char *message, char **names, char **values, size_t entries, size_t *errors, size_t (*callback)(DICTIONARY *dict, char **names, char **values, size_t entries)) {
1201 - fprintf(stderr, "%-40s... ", message);
1491 + fprintf(stderr, "%40s ... ", message);
1492
1493 usec_t started = now_realtime_usec();
1494 size_t errs = callback(dict, names, values, entries);
@@ -1207,7 +1497,7 @@ static usec_t dictionary_unittest_run_and_measure_time(DICTIONARY *dict, char *m
1497
1498 if(callback == dictionary_unittest_destroy) dict = NULL;
1499
1210 - fprintf(stderr, " %zu errors, %zu items in dictionary, %llu usec \n", errs, dict? dictionary_stats_entries(dict):0, dt);
1500 + fprintf(stderr, " %zu errors, %ld items in dictionary, %llu usec \n", errs, dict? dictionary_stats_entries(dict):0, dt);
1501 *errors += errs;
1502 return dt;
1503 }
@@ -1288,6 +1578,171 @@ static void dictionary_unittest_null_dfe(DICTIONARY *dict, char **names, char **
1578 dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach);
1579 }
1580
1581 +
1582 +static int check_dictionary_callback(const char *name, void *value, void *data) {
1583 + (void)name;
1584 + (void)value;
1585 + (void)data;
1586 + return 1;
1587 +}
1588 +
1589 +static size_t check_dictionary(DICTIONARY *dict, size_t entries, size_t linked_list_members) {
1590 + size_t errors = 0;
1591 +
1592 + fprintf(stderr, "dictionary entries %ld, expected %zu...\t\t\t\t\t", dictionary_stats_entries(dict), entries);
1593 + if (dictionary_stats_entries(dict) != (long)entries) {
1594 + fprintf(stderr, "FAILED\n");
1595 + errors++;
1596 + }
1597 + else
1598 + fprintf(stderr, "OK\n");
1599 +
1600 + size_t ll = 0;
1601 + void *t;
1602 + dfe_start_read(dict, t)
1603 + ll++;
1604 + dfe_done(t);
1605 +
1606 + fprintf(stderr, "dictionary foreach entries %zu, expected %zu...\t\t\t\t", ll, entries);
1607 + if(ll != entries) {
1608 + fprintf(stderr, "FAILED\n");
1609 + errors++;
1610 + }
1611 + else
1612 + fprintf(stderr, "OK\n");
1613 +
1614 + ll = dictionary_walkthrough_read(dict, check_dictionary_callback, NULL);
1615 + fprintf(stderr, "dictionary walkthrough entries %zu, expected %zu...\t\t\t\t", ll, entries);
1616 + if(ll != entries) {
1617 + fprintf(stderr, "FAILED\n");
1618 + errors++;
1619 + }
1620 + else
1621 + fprintf(stderr, "OK\n");
1622 +
1623 + ll = dictionary_sorted_walkthrough_read(dict, check_dictionary_callback, NULL);
1624 + fprintf(stderr, "dictionary sorted walkthrough entries %zu, expected %zu...\t\t\t", ll, entries);
1625 + if(ll != entries) {
1626 + fprintf(stderr, "FAILED\n");
1627 + errors++;
1628 + }
1629 + else
1630 + fprintf(stderr, "OK\n");
1631 +
1632 + NAME_VALUE *nv;
1633 + for(ll = 0, nv = dict->first_item; nv ;nv = nv->next)
1634 + ll++;
1635 +
1636 + fprintf(stderr, "dictionary linked list entries %zu, expected %zu...\t\t\t\t", ll, linked_list_members);
1637 + if(ll != linked_list_members) {
1638 + fprintf(stderr, "FAILED\n");
1639 + errors++;
1640 + }
1641 + else
1642 + fprintf(stderr, "OK\n");
1643 +
1644 + return errors;
1645 +}
1646 +
1647 +static int check_name_value_callback(const char *name, void *value, void *data) {
1648 + (void)name;
1649 + return value == data;
1650 +}
1651 +
1652 +static size_t check_name_value(DICTIONARY *dict, NAME_VALUE *nv, const char *name, const char *value, int refcount, NAME_VALUE_FLAGS flags, bool searchable, bool browsable, bool linked) {
1653 + size_t errors = 0;
1654 +
1655 + fprintf(stderr, "NAME_VALUE name is '%s', expected '%s'...\t\t\t\t", nv->name, name);
1656 + if(strcmp(nv->name, name) != 0) {
1657 + fprintf(stderr, "FAILED\n");
1658 + errors++;
1659 + }
1660 + else
1661 + fprintf(stderr, "OK\n");
1662 +
1663 + fprintf(stderr, "NAME_VALUE value is '%s', expected '%s'...\t\t\t", (const char *)nv->value, value);
1664 + if(strcmp((const char *)nv->value, value) != 0) {
1665 + fprintf(stderr, "FAILED\n");
1666 + errors++;
1667 + }
1668 + else
1669 + fprintf(stderr, "OK\n");
1670 +
1671 + fprintf(stderr, "NAME_VALUE refcount is %d, expected %d...\t\t\t\t\t", nv->refcount, refcount);
1672 + if (nv->refcount != refcount) {
1673 + fprintf(stderr, "FAILED\n");
1674 + errors++;
1675 + }
1676 + else
1677 + fprintf(stderr, "OK\n");
1678 +
1679 + fprintf(stderr, "NAME_VALUE flags is %u, expected %u...\t\t\t\t\t", nv->flags, flags);
1680 + if (nv->flags != flags) {
1681 + fprintf(stderr, "FAILED\n");
1682 + errors++;
1683 + }
1684 + else
1685 + fprintf(stderr, "OK\n");
1686 +
1687 + void *v = dictionary_get(dict, name);
1688 + bool found = v == nv->value;
1689 + fprintf(stderr, "NAME_VALUE searchable %5s, expected %5s...\t\t\t\t", found?"true":"false", searchable?"true":"false");
1690 + if(found != searchable) {
1691 + fprintf(stderr, "FAILED\n");
1692 + errors++;
1693 + }
1694 + else
1695 + fprintf(stderr, "OK\n");
1696 +
1697 + found = false;
1698 + void *t;
1699 + dfe_start_read(dict, t) {
1700 + if(t == nv->value) found = true;
1701 + }
1702 + dfe_done(t);
1703 +
1704 + fprintf(stderr, "NAME_VALUE dfe browsable %5s, expected %5s...\t\t\t", found?"true":"false", browsable?"true":"false");
1705 + if(found != browsable) {
1706 + fprintf(stderr, "FAILED\n");
1707 + errors++;
1708 + }
1709 + else
1710 + fprintf(stderr, "OK\n");
1711 +
1712 + found = dictionary_walkthrough_read(dict, check_name_value_callback, nv->value);
1713 + fprintf(stderr, "NAME_VALUE walkthrough browsable %5s, expected %5s...\t\t", found?"true":"false", browsable?"true":"false");
1714 + if(found != browsable) {
1715 + fprintf(stderr, "FAILED\n");
1716 + errors++;
1717 + }
1718 + else
1719 + fprintf(stderr, "OK\n");
1720 +
1721 + found = dictionary_sorted_walkthrough_read(dict, check_name_value_callback, nv->value);
1722 + fprintf(stderr, "NAME_VALUE sorted walkthrough browsable %5s, expected %5s...\t", found?"true":"false", browsable?"true":"false");
1723 + if(found != browsable) {
1724 + fprintf(stderr, "FAILED\n");
1725 + errors++;
1726 + }
1727 + else
1728 + fprintf(stderr, "OK\n");
1729 +
1730 + found = false;
1731 + NAME_VALUE *n;
1732 + for(n = dict->first_item; n ;n = n->next)
1733 + if(n == nv) found = true;
1734 +
1735 + fprintf(stderr, "NAME_VALUE linked %5s, expected %5s...\t\t\t\t", found?"true":"false", linked?"true":"false");
1736 + if(found != linked) {
1737 + fprintf(stderr, "FAILED\n");
1738 + errors++;
1739 + }
1740 + else
1741 + fprintf(stderr, "OK\n");
1742 +
1743 + return errors;
1744 +}
1745 +
1746 int dictionary_unittest(size_t entries) {
1747 if(entries < 10) entries = 10;
1748
@@ -1352,6 +1807,77 @@ int dictionary_unittest(size_t entries) {
1807 dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
1808 dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1809
1810 + // check reference counters
1811 + {
1812 + fprintf(stderr, "\nTesting reference counters:\n");
1813 + dict = dictionary_create(DICTIONARY_FLAG_NONE);
1814 + errors += check_dictionary(dict, 0, 0);
1815 +
1816 + fprintf(stderr, "\nAdding test item to dictionary and acquiring it\n");
1817 + dictionary_set(dict, "test", "ITEM1", 6);
1818 + NAME_VALUE *nv = dictionary_acquire_item(dict, "test");
1819 +
1820 + errors += check_dictionary(dict, 1, 1);
1821 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_NONE, true, true, true);
1822 +
1823 + fprintf(stderr, "\nChecking that reference counters are increased:\n");
1824 + void *t;
1825 + dfe_start_read(dict, t) {
1826 + errors += check_dictionary(dict, 1, 1);
1827 + errors += check_name_value(dict, nv, "test", "ITEM1", 2, NAME_VALUE_FLAG_NONE, true, true, true);
1828 + }
1829 + dfe_done(t);
1830 +
1831 + fprintf(stderr, "\nChecking that reference counters are decreased:\n");
1832 + errors += check_dictionary(dict, 1, 1);
1833 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_NONE, true, true, true);
1834 +
1835 + fprintf(stderr, "\nDeleting the item we have acquired:\n");
1836 + dictionary_del(dict, "test");
1837 +
1838 + errors += check_dictionary(dict, 0, 1);
1839 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
1840 +
1841 + fprintf(stderr, "\nAdding another item with the same name of the item we deleted, while being acquired:\n");
1842 + dictionary_set(dict, "test", "ITEM2", 6);
1843 + errors += check_dictionary(dict, 1, 2);
1844 +
1845 + fprintf(stderr, "\nAcquiring the second item:\n");
1846 + NAME_VALUE *nv2 = dictionary_acquire_item(dict, "test");
1847 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
1848 + errors += check_name_value(dict, nv2, "test", "ITEM2", 1, NAME_VALUE_FLAG_NONE, true, true, true);
1849 +
1850 + fprintf(stderr, "\nReleasing the second item (the first is still acquired):\n");
1851 + dictionary_acquired_item_release(dict, nv2);
1852 + errors += check_dictionary(dict, 1, 2);
1853 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
1854 + errors += check_name_value(dict, nv2, "test", "ITEM2", 0, NAME_VALUE_FLAG_NONE, true, true, true);
1855 +
1856 + fprintf(stderr, "\nDeleting the second item (the first is still acquired):\n");
1857 + dictionary_del(dict, "test");
1858 + errors += check_dictionary(dict, 0, 1);
1859 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_DELETED, false, false, true);
1860 +
1861 + fprintf(stderr, "\nReleasing the first item (which we have already deleted):\n");
1862 + dictionary_acquired_item_release(dict, nv);
1863 + errors += check_dictionary(dict, 0, 0);
1864 +
1865 + fprintf(stderr, "\nAdding again the test item to dictionary and acquiring it\n");
1866 + dictionary_set(dict, "test", "ITEM1", 6);
1867 + nv = dictionary_acquire_item(dict, "test");
1868 +
1869 + errors += check_dictionary(dict, 1, 1);
1870 + errors += check_name_value(dict, nv, "test", "ITEM1", 1, NAME_VALUE_FLAG_NONE, true, true, true);
1871 +
1872 + fprintf(stderr, "\nDestroying the dictionary while we have acquired an item\n");
1873 + dictionary_destroy(dict);
1874 +
1875 + fprintf(stderr, "Releasing the item (on a destroyed dictionary)\n");
1876 + dictionary_acquired_item_release(dict, nv);
1877 + nv = NULL;
1878 + dict = NULL;
1879 + }
1880 +
1881 dictionary_unittest_free_char_pp(names, entries);
1882 dictionary_unittest_free_char_pp(values, entries);
1883
libnetdata/dictionary/dictionary.h
+15 -6
@@ -46,7 +46,11 @@ typedef enum dictionary_flags {
46 DICTIONARY_FLAG_NAME_LINK_DONT_CLONE = (1 << 2), // don't copy the name, just point to the one provided (default: copy)
47 DICTIONARY_FLAG_DONT_OVERWRITE_VALUE = (1 << 3), // don't overwrite values of dictionary items (default: overwrite)
48 DICTIONARY_FLAG_ADD_IN_FRONT = (1 << 4), // add dictionary items at the front of the linked list (default: at the end)
49 - DICTIONARY_FLAG_RESERVED1 = (1 << 5), // this is reserved for DICTIONARY_FLAG_REFERENCE_COUNTERS
49 +
50 + // to change the value of the following, you also need to change the corresponding #defines in dictionary.c
51 + DICTIONARY_FLAG_RESERVED1 = (1 << 29), // reserved for DICTIONARY_FLAG_EXCLUSIVE_ACCESS
52 + DICTIONARY_FLAG_RESERVED2 = (1 << 30), // reserved for DICTIONARY_FLAG_DESTROYED
53 + DICTIONARY_FLAG_RESERVED3 = (1 << 31), // reserved for DICTIONARY_FLAG_DEFER_ALL_DELETIONS
54 } DICTIONARY_FLAGS;
55
56 // Create a dictionary
@@ -95,6 +99,12 @@ extern void *dictionary_get(DICTIONARY *dict, const char *name);
99 // returns -1 if the item was not found in the index
100 extern int dictionary_del(DICTIONARY *dict, const char *name);
101
102 +extern void *dictionary_acquire_item_unsafe(DICTIONARY *dict, const char *name);
103 +extern void *dictionary_acquire_item(DICTIONARY *dict, const char *name);
104 +extern void *dictionary_acquired_item_value(DICTIONARY *dict, void *item);
105 +extern void dictionary_acquired_item_release(DICTIONARY *dict, void *item);
106 +extern void dictionary_acquired_item_release_unsafe(DICTIONARY *dict, void *item);
107 +
108 // UNSAFE functions, without locks
109 // to be used when the user is traversing with the right lock type
110 // Read lock is acquired by dictionary_walktrhough_read() and dfe_start_read()
@@ -155,10 +165,10 @@ typedef DICTFE_CONST struct dictionary_foreach {
165 // same as the return value of dictfe_start() and dictfe_next()
166
167 // the following are for internal use only - to keep track of the point we are
168 + char rw; // the lock mode 'r' or 'w'
169 usec_t started_ut; // the time the caller started iterating (now_realtime_usec())
170 DICTIONARY *dict; // the dictionary upon we work
160 - void *last_position_index; // the internal position index, to remember the position we are at
161 - void *next_position_index; // the internal position index, of the next item
171 + void *last_item; // the item we work on, to remember the position we are at
172 } DICTFE;
173
174 #define dfe_start_read(dict, value) dfe_start_rw(dict, value, 'r')
@@ -182,9 +192,8 @@ extern void * dictionary_foreach_next(DICTFE *dfe);
192 extern usec_t dictionary_foreach_done(DICTFE *dfe);
193
194 // Get statistics about the dictionary
185 -// If DICTIONARY_FLAG_WITH_STATISTICS is not set, these return zero
186 -extern size_t dictionary_stats_allocated_memory(DICTIONARY *dict);
187 -extern size_t dictionary_stats_entries(DICTIONARY *dict);
195 +extern long int dictionary_stats_allocated_memory(DICTIONARY *dict);
196 +extern long int dictionary_stats_entries(DICTIONARY *dict);
197 extern size_t dictionary_stats_inserts(DICTIONARY *dict);
198 extern size_t dictionary_stats_searches(DICTIONARY *dict);
199 extern size_t dictionary_stats_deletes(DICTIONARY *dict);
libnetdata/log/log.c
+17 -8
@@ -6,7 +6,7 @@
6 int web_server_is_multithreaded = 1;
7
8 const char *program_name = "";
9 -uint64_t debug_flags = DEBUG;
9 +uint64_t debug_flags = 0;
10
11 int access_log_syslog = 1;
12 int error_log_syslog = 1;
@@ -691,7 +691,7 @@ void debug_int( const char *file, const char *function, const unsigned long line
691 log_date(date, LOG_DATE_LENGTH);
692
693 va_start( args, fmt );
694 - printf("%s: %s DEBUG : %s : (%04lu@%-10.10s:%-15.15s): ", date, program_name, netdata_thread_tag(), line, file, function);
694 + printf("%s: %s DEBUG : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, netdata_thread_tag(), line, file, function);
695 vprintf(fmt, args);
696 va_end( args );
697 putchar('\n');
@@ -727,8 +727,11 @@ void info_int( const char *file, const char *function, const unsigned long line,
727 log_lock();
728
729 va_start( args, fmt );
730 - if(debug_flags) fprintf(stderr, "%s: %s INFO : %s : (%04lu@%-10.10s:%-15.15s): ", date, program_name, netdata_thread_tag(), line, file, function);
731 - else fprintf(stderr, "%s: %s INFO : %s : ", date, program_name, netdata_thread_tag());
730 +#ifdef NETDATA_INTERNAL_CHECKS
731 + fprintf(stderr, "%s: %s INFO : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, netdata_thread_tag(), line, file, function);
732 +#else
733 + fprintf(stderr, "%s: %s INFO : %s : ", date, program_name, netdata_thread_tag());
734 +#endif
735 vfprintf( stderr, fmt, args );
736 va_end( args );
737
@@ -783,8 +786,11 @@ void error_int( const char *prefix, const char *file, const char *function, cons
786 log_lock();
787
788 va_start( args, fmt );
786 - if(debug_flags) fprintf(stderr, "%s: %s %-5.5s : %s : (%04lu@%-10.10s:%-15.15s): ", date, program_name, prefix, netdata_thread_tag(), line, file, function);
787 - else fprintf(stderr, "%s: %s %-5.5s : %s : ", date, program_name, prefix, netdata_thread_tag());
789 +#ifdef NETDATA_INTERNAL_CHECKS
790 + fprintf(stderr, "%s: %s %-5.5s : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, prefix, netdata_thread_tag(), line, file, function);
791 +#else
792 + fprintf(stderr, "%s: %s %-5.5s : %s : ", date, program_name, prefix, netdata_thread_tag());
793 +#endif
794 vfprintf( stderr, fmt, args );
795 va_end( args );
796
@@ -826,8 +832,11 @@ void fatal_int( const char *file, const char *function, const unsigned long line
832 log_lock();
833
834 va_start( args, fmt );
829 - if(debug_flags) fprintf(stderr, "%s: %s FATAL : %s : (%04lu@%-10.10s:%-15.15s): ", date, program_name, thread_tag, line, file, function);
830 - else fprintf(stderr, "%s: %s FATAL : %s : ", date, program_name, thread_tag);
835 +#ifdef NETDATA_INTERNAL_CHECKS
836 + fprintf(stderr, "%s: %s FATAL : %s : (%04lu@%-20.20s:%-15.15s): ", date, program_name, thread_tag, line, file, function);
837 +#else
838 + fprintf(stderr, "%s: %s FATAL : %s : ", date, program_name, thread_tag);
839 +#endif
840 vfprintf( stderr, fmt, args );
841 va_end( args );
842
libnetdata/log/log.h
+2 -4
@@ -47,10 +47,6 @@ extern "C" {
47 #define D_ACLK_SYNC 0x0000000800000000
48 #define D_SYSTEM 0x8000000000000000
49
50 -//#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
51 -//#define DEBUG 0xffffffff
52 -#define DEBUG (0)
53 -
50 extern int web_server_is_multithreaded;
51
52 extern uint64_t debug_flags;
@@ -86,8 +82,10 @@ static inline void debug_dummy(void) {}
82
83 #ifdef NETDATA_INTERNAL_CHECKS
84 #define debug(type, args...) do { if(unlikely(debug_flags & type)) debug_int(__FILE__, __FUNCTION__, __LINE__, ##args); } while(0)
85 +#define internal_error(condition, args...) do { if(unlikely(condition)) error_int("INTERNAL ERROR", __FILE__, __FUNCTION__, __LINE__, ##args); } while(0)
86 #else
87 #define debug(type, args...) debug_dummy()
88 +#define internal_error(args...) debug_dummy()
89 #endif
90
91 #define info(args...) info_int(__FILE__, __FUNCTION__, __LINE__, ##args)