master
h 247 lines 7.93 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #ifndef NETDATA_DICTIONARY_REFCOUNT_H
4 #define NETDATA_DICTIONARY_REFCOUNT_H
5
6 #include "dictionary-internals.h"
7
8 // ----------------------------------------------------------------------------
9 // reference counters
10
11 static inline size_t reference_counter_init(DICTIONARY *dict __maybe_unused) {
12 // allocate memory required for reference counters
13 // return number of bytes
14 return 0;
15 }
16
17 static inline size_t reference_counter_free(DICTIONARY *dict __maybe_unused) {
18 // free memory required for reference counters
19 // return number of bytes
20 return 0;
21 }
22
23 static inline void item_acquire(DICTIONARY *dict, DICTIONARY_ITEM *item) {
24 REFCOUNT refcount;
25
26 if(unlikely(is_dictionary_single_threaded(dict)))
27 refcount = ++item->refcount;
28
29 else
30 // increment the refcount
31 refcount = __atomic_add_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST);
32
33
34 if(refcount <= 0) {
35 dictionary_internal_error(true, dict,
36 "DICTIONARY: attempted to acquire item which is deleted (refcount = %d): '%s'",
37 refcount - 1,
38 item_get_name(item));
39
40 fatal(
41 "DICTIONARY: request to acquire item '%s', which is deleted (refcount = %d)!",
42 item_get_name(item),
43 refcount - 1);
44 }
45
46 if(refcount == 1) {
47 // referenced items counts number of unique items referenced
48 // so, we increase it only when refcount == 1
49 DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
50
51 // if this is a deleted item, but the counter increased to 1
52 // we need to remove it from the pending items to delete
53 if(item_flag_check(item, ITEM_FLAG_DELETED))
54 DICTIONARY_PENDING_DELETES_MINUS1(dict);
55 }
56
57 #ifdef FSANITIZE_ADDRESS
58 // Add a stacktrace for this acquisition point
59 stacktrace_array_add(&item->stacktraces, 1);
60 #endif
61 }
62
63 static inline void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item) {
64 // this function may be called without any lock on the dictionary
65 // or even when someone else has 'write' lock on the dictionary
66
67 bool is_deleted;
68 REFCOUNT refcount;
69
70 if(unlikely(is_dictionary_single_threaded(dict))) {
71 is_deleted = item->flags & ITEM_FLAG_DELETED;
72 refcount = --item->refcount;
73 }
74 else {
75 // get the flags before decrementing any reference counters
76 // (the other way around may lead to use-after-free)
77 is_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
78
79 // decrement the refcount
80 refcount = __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE);
81 }
82
83 if(refcount < 0) {
84 dictionary_internal_error(true, dict,
85 "DICTIONARY: attempted to release item without references (refcount = %d): '%s'",
86 refcount + 1,
87 item_get_name(item));
88
89 fatal(
90 "DICTIONARY: attempted to release item '%s' without references (refcount = %d)",
91 item_get_name(item),
92 refcount + 1);
93 }
94
95 if(refcount == 0) {
96
97 if(is_deleted)
98 DICTIONARY_PENDING_DELETES_PLUS1(dict);
99
100 // referenced items counts number of unique items referenced
101 // so, we decrease it only when refcount == 0
102 DICTIONARY_REFERENCED_ITEMS_MINUS1(dict);
103 }
104 }
105
106 static inline int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock) {
107 size_t spins = 0;
108 REFCOUNT refcount, desired;
109
110 int ret = RC_ITEM_OK;
111
112 refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
113
114 do {
115 spins++;
116
117 if(refcount < 0) {
118 // we can't use this item
119 ret = RC_ITEM_IS_CURRENTLY_BEING_DELETED;
120 break;
121 }
122
123 if(item_flag_check(item, ITEM_FLAG_DELETED)) {
124 // we can't use this item
125 ret = RC_ITEM_MARKED_FOR_DELETION;
126 break;
127 }
128
129 desired = refcount + 1;
130
131 } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
132
133 // if ret == ITEM_OK, we acquired the item
134
135 if(ret == RC_ITEM_OK) {
136 if (unlikely(is_view_dictionary(dict) &&
137 item_shared_flag_check(item, ITEM_FLAG_DELETED) &&
138 !item_flag_check(item, ITEM_FLAG_DELETED))) {
139 // but, we can't use this item
140
141 if (having_index_lock) {
142 // delete it from the hashtable
143 if(hashtable_delete_unsafe(dict, item_get_name(item), item->key_len, item) == 0)
144 netdata_log_error("DICTIONARY: INTERNAL ERROR VIEW: tried to delete item with name '%s', "
145 "name_len %u that is not in the index",
146 item_get_name(item), (KEY_LEN_TYPE)(item->key_len));
147 else
148 pointer_del(dict, item);
149
150 // mark it in our dictionary as deleted too,
151 // this is safe to be done here, because we have got
152 // a reference counter on item
153 dict_item_set_deleted(dict, item);
154
155 // decrement the refcount we incremented above
156 if (__atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE) == 0) {
157 // this is a deleted item, and we are the last one
158 DICTIONARY_PENDING_DELETES_PLUS1(dict);
159 }
160
161 // do not touch the item below this point
162 } else {
163 // this is traversal / walkthrough
164 // decrement the refcount we incremented above
165 __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE);
166 }
167
168 return RC_ITEM_MARKED_FOR_DELETION;
169 }
170
171 if(desired == 1)
172 DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
173
174 #ifdef FSANITIZE_ADDRESS
175 // Add a stacktrace for this acquisition point
176 stacktrace_array_add(&item->stacktraces, 1);
177 #endif
178 }
179
180 if(unlikely(spins > 1))
181 DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, spins - 1);
182
183 return ret;
184 }
185
186 // if a dictionary item can be deleted, return true, otherwise return false
187 // we use the private reference counter
188 static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item) {
189 // if we can set refcount to REFCOUNT_DELETING, we can delete this item
190
191 size_t spins = 0;
192 REFCOUNT refcount, desired = REFCOUNT_DELETED;
193
194 int ret = RC_ITEM_OK;
195
196 refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
197
198 do {
199 spins++;
200
201 if(refcount < 0) {
202 // we can't use this item
203 ret = RC_ITEM_IS_CURRENTLY_BEING_DELETED;
204 break;
205 }
206
207 if(refcount > 0) {
208 // we can't delete this
209 ret = RC_ITEM_IS_REFERENCED;
210 break;
211 }
212
213 if(item_flag_check(item, ITEM_FLAG_BEING_CREATED)) {
214 // we can't use this item
215 ret = RC_ITEM_IS_CURRENTLY_BEING_CREATED;
216 break;
217 }
218 } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
219
220 #ifdef NETDATA_INTERNAL_CHECKS
221 if(ret == RC_ITEM_OK)
222 item->deleter_pid = gettid_cached();
223 #endif
224
225 if(unlikely(spins > 1))
226 DICTIONARY_STATS_DELETE_SPINS_PLUS(dict, spins - 1);
227
228 return ret;
229 }
230
231 // if a dictionary item can be freed, return true, otherwise return false
232 // we use the shared reference counter
233 static inline bool item_shared_release_and_check_if_it_can_be_freed(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item) {
234 // if we can set refcount to REFCOUNT_DELETING, we can delete this item
235
236 REFCOUNT links = __atomic_sub_fetch(&item->shared->links, 1, __ATOMIC_RELEASE);
237 if(links == 0 && __atomic_compare_exchange_n(&item->shared->links, &links, REFCOUNT_DELETED, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
238
239 // we can delete it
240 return true;
241 }
242
243 // we can't delete it
244 return false;
245 }
246
247 #endif //NETDATA_DICTIONARY_REFCOUNT_H