@cryptotaxi247 / netdata-1 / commits / 3a2dd8769

fix incomplete implementation of journal watcher (#19592)

* fix incomplete implementation for journal watcher * debug messages logged as info to verify operation * detect symlinks to directories * added error handling to all inotify calls * fix for amazon linux not supporting IN_MASK_CREATE * fix cleanup when removing directories and symlinks to directories

Costa Tsaousis committed Feb 7, 2025 at 18:13 UTC 3a2dd87697d6fa724dc2be2d1baf43fff191096d
2 files changed +269 -73
src/collectors/systemd-journal.plugin/systemd-journal-watcher.c
+265 -71
@@ -3,11 +3,53 @@
3 #include "systemd-internals.h"
4 #include <sys/inotify.h>
5
6 -#define EVENT_SIZE (sizeof(struct inotify_event))
6 #define INITIAL_WATCHES 256
7
8 #define WATCH_FOR (IN_CREATE | IN_MODIFY | IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVED_TO | IN_UNMOUNT)
9
10 +typedef uint32_t INOTIFY_MASK;
11 +
12 +ENUM_STR_MAP_DEFINE(INOTIFY_MASK) = {
13 + // helpers (combine multiple flags)
14 + // must be first in the list
15 + {.id = IN_ALL_EVENTS, .name = "IN_ALL_EVENTS"},
16 + {.id = IN_CLOSE, .name = "IN_CLOSE"},
17 + {.id = IN_MOVE, .name = "IN_MOVE"},
18 +
19 + // individual flags
20 + {.id = IN_ACCESS, .name = "IN_ACCESS"},
21 + {.id = IN_MODIFY, .name = "IN_MODIFY"},
22 + {.id = IN_ATTRIB, .name = "IN_ATTRIB"},
23 + {.id = IN_CLOSE_WRITE, .name = "IN_CLOSE_WRITE"},
24 + {.id = IN_CLOSE_NOWRITE, .name = "IN_CLOSE_NOWRITE"},
25 + {.id = IN_OPEN, .name = "IN_OPEN"},
26 + {.id = IN_MOVED_FROM, .name = "IN_MOVED_FROM"},
27 + {.id = IN_MOVED_TO, .name = "IN_MOVED_TO"},
28 + {.id = IN_CREATE, .name = "IN_CREATE"},
29 + {.id = IN_DELETE, .name = "IN_DELETE"},
30 + {.id = IN_DELETE_SELF, .name = "IN_DELETE_SELF"},
31 + {.id = IN_MOVE_SELF, .name = "IN_MOVE_SELF"},
32 + {.id = IN_UNMOUNT, .name = "IN_UNMOUNT"},
33 + {.id = IN_Q_OVERFLOW, .name = "IN_Q_OVERFLOW"},
34 + {.id = IN_IGNORED, .name = "IN_IGNORED"},
35 + {.id = IN_ONLYDIR, .name = "IN_ONLYDIR"},
36 + {.id = IN_DONT_FOLLOW, .name = "IN_DONT_FOLLOW"},
37 + {.id = IN_EXCL_UNLINK, .name = "IN_EXCL_UNLINK"},
38 +#ifdef IN_MASK_CREATE
39 + {.id = IN_MASK_CREATE, .name = "IN_MASK_CREATE"},
40 +#endif
41 + {.id = IN_MASK_ADD, .name = "IN_MASK_ADD"},
42 + {.id = IN_ISDIR, .name = "IN_ISDIR"},
43 + {.id = IN_ONESHOT, .name = "IN_ONESHOT"},
44 +
45 + // terminator
46 + {.id = 0, .name = NULL}
47 +};
48 +
49 +BITMAP_STR_DEFINE_FUNCTIONS(INOTIFY_MASK, 0, "UNKNOWN");
50 +
51 +DEFINE_JUDYL_TYPED(SYMLINKED_DIRS, STRING *);
52 +
53 typedef struct watch_entry {
54 int slot;
55
@@ -25,6 +67,7 @@ typedef struct {
67
68 size_t errors;
69
70 + SYMLINKED_DIRS_JudyLSet symlinkedDirs;
71 DICTIONARY *pending;
72 } Watcher;
73
@@ -68,6 +111,7 @@ static void free_slot(Watcher *watcher, WatchEntry *t) {
111 static int add_watch(Watcher *watcher, int inotifyFd, const char *path) {
112 WatchEntry *t = get_slot(watcher);
113
114 + errno_clear();
115 t->wd = inotify_add_watch(inotifyFd, path, WATCH_FOR);
116 if (t->wd == -1) {
117 nd_log(NDLS_COLLECTORS, NDLP_ERR,
@@ -95,6 +139,8 @@ static int add_watch(Watcher *watcher, int inotifyFd, const char *path) {
139 }
140
141 static void remove_watch(Watcher *watcher, int inotifyFd, int wd) {
142 + errno_clear();
143 +
144 int i;
145 for (i = 0; i < watcher->watchCount; ++i) {
146 if (watcher->watchList[i].wd == wd) {
@@ -103,7 +149,9 @@ static void remove_watch(Watcher *watcher, int inotifyFd, int wd) {
149 "JOURNAL WATCHER: removing watch from directory: '%s'",
150 watcher->watchList[i].path);
151
106 - inotify_rm_watch(inotifyFd, watcher->watchList[i].wd);
152 + if(inotify_rm_watch(inotifyFd, watcher->watchList[i].wd) == -1)
153 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "JOURNAL WATCHER: inotify_rm_watch() returned -1");
154 +
155 free_slot(watcher, &watcher->watchList[i]);
156 return;
157 }
@@ -117,7 +165,8 @@ static void remove_watch(Watcher *watcher, int inotifyFd, int wd) {
165 static void free_watches(Watcher *watcher, int inotifyFd) {
166 for (int i = 0; i < watcher->watchCount; ++i) {
167 if (watcher->watchList[i].wd != -1) {
120 - inotify_rm_watch(inotifyFd, watcher->watchList[i].wd);
168 + if(inotify_rm_watch(inotifyFd, watcher->watchList[i].wd) == -1)
169 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "JOURNAL WATCHER: inotify_rm_watch() returned -1");
170 free_slot(watcher, &watcher->watchList[i]);
171 }
172 }
@@ -128,6 +177,17 @@ static void free_watches(Watcher *watcher, int inotifyFd) {
177 watcher->pending = NULL;
178 }
179
180 +static void free_symlinked_dirs(Watcher *watcher) {
181 + Word_t idx = 0;
182 + STRING *value;
183 + while((value = SYMLINKED_DIRS_FIRST(&watcher->symlinkedDirs, &idx))) {
184 + SYMLINKED_DIRS_DEL(&watcher->symlinkedDirs, idx);
185 + STRING *key = (STRING *)idx;
186 + string_freez(key);
187 + string_freez(value);
188 + }
189 +}
190 +
191 static char* get_path_from_wd(Watcher *watcher, int wd) {
192 for (int i = 0; i < watcher->watchCount; ++i) {
193 if (watcher->watchList[i].wd == wd)
@@ -148,14 +208,31 @@ static bool is_directory_watched(Watcher *watcher, const char *path) {
208 static void watch_directory_and_subdirectories(Watcher *watcher, int inotifyFd, const char *basePath) {
209 DICTIONARY *dirs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
210
151 - journal_directory_scan_recursively(NULL, dirs, basePath, 0);
211 + // First resolve any symlinks in the base path
212 + char real_path[PATH_MAX];
213 + if (realpath(basePath, real_path) == NULL) {
214 + // If realpath fails, try using the original path
215 + strncpyz(real_path, basePath, sizeof(real_path));
216 + }
217 +
218 + journal_directory_scan_recursively(NULL, dirs, real_path, 0);
219
220 void *x;
221 dfe_start_read(dirs, x) {
222 const char *dirname = x_dfe.name;
156 - // Check if this directory is already being watched
157 - if (!is_directory_watched(watcher, dirname)) {
158 - add_watch(watcher, inotifyFd, dirname);
223 + char resolved_path[PATH_MAX];
224 +
225 + // Resolve symlinks for each subdirectory
226 + if (realpath(dirname, resolved_path) != NULL) {
227 + // Check if this directory is already being watched
228 + if (!is_directory_watched(watcher, resolved_path)) {
229 + add_watch(watcher, inotifyFd, resolved_path);
230 + }
231 + } else {
232 + // If realpath fails, try with original path
233 + if (!is_directory_watched(watcher, dirname)) {
234 + add_watch(watcher, inotifyFd, dirname);
235 + }
236 }
237 }
238 dfe_done(x);
@@ -177,15 +254,18 @@ static bool is_subpath(const char *path, const char *subpath) {
254 void remove_directory_watch(Watcher *watcher, int inotifyFd, const char *dirPath) {
255 for (int i = 0; i < watcher->watchCount; ++i) {
256 WatchEntry *t = &watcher->watchList[i];
180 - if (t->wd != -1 && is_subpath(t->path, dirPath)) {
181 - inotify_rm_watch(inotifyFd, t->wd);
257 + if (t->wd != -1 && is_subpath(dirPath, t->path)) {
258 + if(inotify_rm_watch(inotifyFd, t->wd) == -1)
259 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "JOURNAL WATCHER: inotify_rm_watch() on path '%s' returned -1", t->path);
260 + else
261 + nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "JOURNAL WATCHER: stopped watching directory '%s'", t->path);
262 free_slot(watcher, t);
263 }
264 }
265
266 struct journal_file *jf;
267 dfe_start_write(journal_files_registry, jf) {
188 - if(is_subpath(jf->filename, dirPath))
268 + if(is_subpath(dirPath, jf->filename))
269 dictionary_del(journal_files_registry, jf->filename);
270 }
271 dfe_done(jf);
@@ -194,22 +274,38 @@ void remove_directory_watch(Watcher *watcher, int inotifyFd, const char *dirPath
274 }
275
276 void process_event(Watcher *watcher, int inotifyFd, struct inotify_event *event) {
277 + errno_clear();
278 +
279 if(!event->len) {
198 - nd_log(NDLS_COLLECTORS, NDLP_NOTICE
199 - , "JOURNAL WATCHER: received event with mask %u and len %u (this is zero) for path: '%s' - ignoring it."
200 - , event->mask, event->len, event->name);
280 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
281 + INOTIFY_MASK_2buffer(wb, event->mask, ", ");
282 + nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
283 + "JOURNAL WATCHER: received event with mask %u (%s) and len %u (this is zero) - ignoring it.",
284 + event->mask, buffer_tostring(wb), event->len);
285 return;
286 }
287
288 char *dirPath = get_path_from_wd(watcher, event->wd);
289 if(!dirPath) {
290 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
291 + INOTIFY_MASK_2buffer(wb, event->mask, ", ");
292 nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
207 - "JOURNAL WATCHER: received event with mask %u and len %u for path: '%s' - "
208 - "but we can't find its watch descriptor - ignoring it."
209 - , event->mask, event->len, event->name);
293 + "JOURNAL WATCHER: received event with mask %u (%s) and len %u for path: '%s' - "
294 + "but we can't find its watch descriptor - ignoring it.",
295 + event->mask, buffer_tostring(wb), event->len, event->name);
296 return;
297 }
298
299 +#ifdef NETDATA_INTERNAL_CHECKS
300 + {
301 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
302 + INOTIFY_MASK_2buffer(wb, event->mask, ", ");
303 + nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
304 + "JOURNAL WATCHER: received event with mask %u (%s) for path: '%s' inside '%s'",
305 + event->mask, buffer_tostring(wb), event->name, dirPath);
306 + }
307 +#endif
308 +
309 if(event->mask & IN_DELETE_SELF) {
310 remove_watch(watcher, inotifyFd, event->wd);
311 return;
@@ -217,46 +313,115 @@ void process_event(Watcher *watcher, int inotifyFd, struct inotify_event *event)
313
314 static __thread char fullPath[PATH_MAX];
315 snprintfz(fullPath, sizeof(fullPath), "%s/%s", dirPath, event->name);
220 - // fullPath contains the full path to the file
316
222 - size_t len = strlen(event->name);
317 + bool is_dir = event->mask & IN_ISDIR;
318 + char resolved_path[PATH_MAX];
319 + const char *path_to_use = fullPath;
320 +
321 + if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
322 + // Give the system a moment to establish the symlink
323 + sleep_usec(1000); // 1ms sleep
324 +
325 + struct stat st;
326 + if (lstat(fullPath, &st) == 0) {
327 + if (S_ISLNK(st.st_mode)) {
328 + // It's a symlink - resolve it
329 + if (realpath(fullPath, resolved_path) != NULL) {
330 + path_to_use = resolved_path;
331 +
332 + // Check if it points to a directory
333 + if (stat(resolved_path, &st) == 0 && S_ISDIR(st.st_mode)) {
334 + is_dir = true;
335 +
336 + STRING *fullPathString = string_strdupz(fullPath);
337 + STRING *symlinked = SYMLINKED_DIRS_GET(&watcher->symlinkedDirs, (uintptr_t)fullPathString);
338 + if (!symlinked) {
339 + SYMLINKED_DIRS_SET(
340 + &watcher->symlinkedDirs, (uintptr_t)fullPathString, string_strdupz(resolved_path));
341 +
342 + // we leave fullPathString allocated, as it's now in the JudyL set
343 +
344 + nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
345 + "JOURNAL WATCHER: New symlinked directory created: '%s' -> '%s'",
346 + fullPath, resolved_path);
347 + }
348 + else if (string_strcmp(symlinked, resolved_path) != 0) {
349 + SYMLINKED_DIRS_SET(
350 + &watcher->symlinkedDirs, (uintptr_t)fullPathString, string_strdupz(resolved_path));
351 +
352 + nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
353 + "JOURNAL WATCHER: Updated symlinked directory: '%s' -> '%s' (was '%s')",
354 + fullPath, resolved_path, string2str(symlinked));
355 +
356 + string_freez(symlinked);
357 +
358 + // we need to free this, since it was already in the JudyL set
359 + string_freez(fullPathString);
360 + }
361 + else
362 + string_freez(fullPathString); // we don't need it anymore
363 + }
364 + }
365 + }
366 + }
367 + }
368 + else if(event->mask & IN_DELETE) {
369 + // Check if it was a symlink
370 + STRING *fullPathString = string_strdupz(fullPath);
371 + STRING *symlinked = SYMLINKED_DIRS_GET(&watcher->symlinkedDirs, (uintptr_t)fullPathString);
372 + if (symlinked) {
373 + strncpyz(resolved_path, string2str(symlinked), sizeof(resolved_path) - 1);
374 + path_to_use = resolved_path;
375 + SYMLINKED_DIRS_DEL(&watcher->symlinkedDirs, (uintptr_t)fullPathString);
376 + string_freez(fullPathString); // to remove also the one referenced in the JudyL set
377 + string_freez(symlinked);
378 + is_dir = true;
379
224 - if(event->mask & IN_ISDIR) {
380 + nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
381 + "JOURNAL WATCHER: Deleted symlinked directory: '%s' -> '%s'",
382 + fullPath, resolved_path);
383 + }
384 + string_freez(fullPathString); // the one we allocated above
385 + }
386 +
387 + if(is_dir) {
388 if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
226 - // A directory is deleted or moved out
389 nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
228 - "JOURNAL WATCHER: Directory deleted or moved out: '%s'",
229 - fullPath);
390 + "JOURNAL WATCHER: Directory deleted or moved out: '%s'",
391 + path_to_use);
392
231 - // Remove the watch - implement this function based on how you manage your watches
232 - remove_directory_watch(watcher, inotifyFd, fullPath);
393 + remove_directory_watch(watcher, inotifyFd, path_to_use);
394 }
395 else if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
235 - // A new directory is created or moved in
396 nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
237 - "JOURNAL WATCHER: New directory created or moved in: '%s'",
238 - fullPath);
397 + "JOURNAL WATCHER: New directory created or moved in: '%s'",
398 + path_to_use);
399
240 - // Start watching the new directory - recursive watch
241 - watch_directory_and_subdirectories(watcher, inotifyFd, fullPath);
400 + watch_directory_and_subdirectories(watcher, inotifyFd, path_to_use);
401 }
243 - else
402 + else {
403 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
404 + INOTIFY_MASK_2buffer(wb, event->mask, ", ");
405 nd_log(NDLS_COLLECTORS, NDLP_WARNING,
245 - "JOURNAL WATCHER: Received unhandled event with mask %u for directory '%s'",
246 - event->mask, fullPath);
406 + "JOURNAL WATCHER: Received unhandled event with mask %u (%s) for directory '%s'",
407 + event->mask, buffer_tostring(wb), path_to_use);
408 + }
409 }
248 - else if(is_journal_file(event->name, (ssize_t)len, NULL)) {
249 - // It is a file that ends in .journal
250 - // add it to our pending list
251 - dictionary_set(watcher->pending, fullPath, NULL, 0);
410 + else if(is_journal_file(event->name, (ssize_t)strlen(event->name), NULL)) {
411 + dictionary_set(watcher->pending, path_to_use, NULL, 0);
412 }
253 - else
413 + else {
414 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
415 + INOTIFY_MASK_2buffer(wb, event->mask, ", ");
416 nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
255 - "JOURNAL WATCHER: ignoring event with mask %u for file '%s'",
256 - event->mask, fullPath);
417 + "JOURNAL WATCHER: ignoring event with mask %u (%s) for file '%s' ('%s')",
418 + event->mask, buffer_tostring(wb), path_to_use, fullPath);
419 + }
420 }
421
422 static void process_pending(Watcher *watcher) {
423 + errno_clear();
424 +
425 void *x;
426 dfe_start_write(watcher->pending, x) {
427 struct stat info;
@@ -298,17 +463,63 @@ void journal_watcher_restart(void) {
463 __atomic_add_fetch(&journal_watcher_wanted_session_id, 1, __ATOMIC_RELAXED);
464 }
465
466 +static bool process_inotify_events(struct buffered_reader *reader, Watcher *watcher, int inotifyFd) {
467 + errno_clear();
468 +
469 + bool unmount_event = false;
470 + ssize_t processed = 0;
471 +
472 + // Process as many complete events as we can
473 + while (processed + (ssize_t)sizeof(struct inotify_event) <= reader->read_len) {
474 + struct inotify_event *event = (struct inotify_event *)(reader->read_buffer + processed);
475 +
476 + if(event->len > NAME_MAX + 1) {
477 + // The event length is impossibly large
478 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
479 + "JOURNAL WATCHER: received impossibly large event length %u - restarting",
480 + event->len);
481 + return true; // force a restart
482 + }
483 +
484 + // Check if we have the complete event including the name
485 + ssize_t total_size = (ssize_t)sizeof(struct inotify_event) + event->len;
486 + if (processed + total_size > reader->read_len)
487 + break; // Wait for more data
488 +
489 + if(event->mask & IN_UNMOUNT) {
490 + unmount_event = true;
491 + break;
492 + }
493 +
494 + process_event(watcher, inotifyFd, event);
495 + processed += total_size;
496 + }
497 +
498 + // If we have unprocessed data, move it to the start
499 + if (processed < reader->read_len) {
500 + memmove(reader->read_buffer,
501 + reader->read_buffer + processed,
502 + reader->read_len - processed);
503 + reader->read_len -= processed;
504 + }
505 + else
506 + reader->read_len = 0;
507 +
508 + reader->read_buffer[reader->read_len] = '\0';
509 + return unmount_event;
510 +}
511 +
512 void *journal_watcher_main(void *arg __maybe_unused) {
513 while(1) {
514 size_t journal_watcher_session_id = __atomic_load_n(&journal_watcher_wanted_session_id, __ATOMIC_RELAXED);
515
516 Watcher watcher = {
306 - .watchList = mallocz(INITIAL_WATCHES * sizeof(WatchEntry)),
307 - .freeList = NULL,
308 - .watchCount = 0,
309 - .watchListSize = INITIAL_WATCHES,
310 - .pending = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE|DICT_OPTION_SINGLE_THREADED),
311 - .errors = 0,
517 + .watchList = mallocz(INITIAL_WATCHES * sizeof(WatchEntry)),
518 + .freeList = NULL,
519 + .watchCount = 0,
520 + .watchListSize = INITIAL_WATCHES,
521 + .pending = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE|DICT_OPTION_SINGLE_THREADED),
522 + .errors = 0,
523 };
524
525 int inotifyFd = inotify_init();
@@ -325,42 +536,24 @@ void *journal_watcher_main(void *arg __maybe_unused) {
536
537 usec_t last_headers_update_ut = now_monotonic_usec();
538 struct buffered_reader reader;
539 + buffered_reader_init(&reader);
540 +
541 while (journal_watcher_session_id == __atomic_load_n(&journal_watcher_wanted_session_id, __ATOMIC_RELAXED)) {
542 buffered_reader_ret_t rc = buffered_reader_read_timeout(
543 &reader, inotifyFd, SYSTEMD_JOURNAL_EXECUTE_WATCHER_PENDING_EVERY_MS, false);
544
332 - if (rc != BUFFERED_READER_READ_OK && rc != BUFFERED_READER_READ_POLL_TIMEOUT) {
333 - nd_log(NDLS_COLLECTORS, NDLP_CRIT,
545 + if(rc == BUFFERED_READER_READ_OK || rc == BUFFERED_READER_READ_BUFFER_FULL) {
546 + if (process_inotify_events(&reader, &watcher, inotifyFd))
547 + break;
548 + }
549 + else if (rc != BUFFERED_READER_READ_POLL_TIMEOUT) {
550 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
551 "JOURNAL WATCHER: cannot read inotify events, buffered_reader_read_timeout() returned %d - "
552 "restarting the watcher.",
553 rc);
554 break;
555 }
556
340 - if(rc == BUFFERED_READER_READ_OK) {
341 - bool unmount_event = false;
342 -
343 - ssize_t i = 0;
344 - while (i < reader.read_len) {
345 - struct inotify_event *event = (struct inotify_event *) &reader.read_buffer[i];
346 -
347 - if(event->mask & IN_UNMOUNT) {
348 - unmount_event = true;
349 - break;
350 - }
351 -
352 - process_event(&watcher, inotifyFd, event);
353 - i += (ssize_t)EVENT_SIZE + event->len;
354 - }
355 -
356 - reader.read_buffer[0] = '\0';
357 - reader.read_len = 0;
358 - reader.pos = 0;
359 -
360 - if(unmount_event)
361 - break;
362 - }
363 -
557 usec_t ut = now_monotonic_usec();
558 if (dictionary_entries(watcher.pending) && (rc == BUFFERED_READER_READ_POLL_TIMEOUT ||
559 last_headers_update_ut + (SYSTEMD_JOURNAL_EXECUTE_WATCHER_PENDING_EVERY_MS * USEC_PER_MS) <= ut)) {
@@ -376,6 +569,7 @@ void *journal_watcher_main(void *arg __maybe_unused) {
569
570 close(inotifyFd);
571 free_watches(&watcher, inotifyFd);
572 + free_symlinked_dirs(&watcher);
573
574 // this will scan the directories and cleanup the registry
575 journal_files_registry_update();
src/libnetdata/dictionary/dictionary-item.h
+4 -2
@@ -216,8 +216,6 @@ static inline size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM
216 size_t item_size = 0, key_size = 0, value_size = 0;
217
218 key_size += item->key_len;
219 - if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
220 - item_free_name(dict, item);
219
220 if(item_shared_release_and_check_if_it_can_be_freed(dict, item)) {
221 dictionary_execute_delete_callback(dict, item);
@@ -234,6 +232,10 @@ static inline size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM
232 item_size += sizeof(DICTIONARY_ITEM_SHARED);
233 }
234
235 + // free the name after calling the delete callback
236 + if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
237 + item_free_name(dict, item);
238 +
239 aral_freez(dict_items_aral, item);
240
241 item_size += sizeof(DICTIONARY_ITEM);