@cryptotaxi247 / netdata-1 / commits / e9b9fb75c

Free all memory on exit (#19821)

* destroy caches * cleanup the mrg and all hosts * cleanup uuidmap * cleanup inline functions and replication * Add destroy_aclk_config function to free aclk_config in RRDHOST * flush all hot pages after the collection has been finalized * increase daemon status file version to 13 --------- Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com>

Costa Tsaousis committed Mar 11, 2025 at 08:41 UTC e9b9fb75c4e95b11395fdd4e6333dd38c84f03b5
18 files changed +234 -39
src/daemon/daemon-shutdown.c
+38 -1
@@ -220,7 +220,7 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
220 #ifdef ENABLE_DBENGINE
221 if(!ret && dbengine_enabled)
222 // flush all dirty pages now that all collectors and streaming completed
223 - rrdeng_flush_everything_and_wait(false, false, false);
223 + rrdeng_flush_everything_and_wait(false, false, true);
224 #endif
225
226 service_wait_exit(SERVICE_REPLICATION, 3 * USEC_PER_SEC);
@@ -322,6 +322,43 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
322 daemon_status_file_shutdown_step(NULL);
323 daemon_status_file_update_status(DAEMON_STATUS_EXITED);
324
325 +#if defined(FSANITIZE_ADDRESS)
326 + fprintf(stderr, "\n");
327 +
328 + fprintf(stderr, "Freeing all RRDHOSTs...\n");
329 + rrdhost_free_all();
330 +
331 + fprintf(stderr, "Cleaning up destroyed dictionaries...\n");
332 + if(cleanup_destroyed_dictionaries())
333 + fprintf(stderr, "WARNING: There are still dictionaries with references in them, that cannot be destroyed.\n");
334 +
335 + // destroy the caches in reverse order (extent and open depend on main cache)
336 + fprintf(stderr, "Destroying extent cache (PGC)...\n");
337 + pgc_destroy(extent_cache);
338 + fprintf(stderr, "Destroying open cache (PGC)...\n");
339 + pgc_destroy(open_cache);
340 + fprintf(stderr, "Destroying main cache (PGC)...\n");
341 + pgc_destroy(main_cache);
342 +
343 + fprintf(stderr, "Destroying metrics registry (MRG)...\n");
344 + size_t metrics_referenced = mrg_destroy(main_mrg);
345 + if(metrics_referenced)
346 + fprintf(stderr, "WARNING: MRG had %zu metrics referenced.\n",
347 + metrics_referenced);
348 +
349 + fprintf(stderr, "Destroying UUIDMap...\n");
350 + size_t uuid_referenced = uuidmap_destroy();
351 + if(uuid_referenced)
352 + fprintf(stderr, "WARNING: UUIDMAP had %zu UUIDs referenced.\n",
353 + uuid_referenced);
354 +
355 + // strings_destroy();
356 + // functions_destroy();
357 + // dyncfg_destroy();
358 +
359 + fprintf(stderr, "All done, exiting...\n");
360 +#endif
361 +
362 #ifdef OS_WINDOWS
363 curl_global_cleanup();
364 return;
src/daemon/daemon-status-file.c
+1 -1
@@ -9,7 +9,7 @@
9 #include <openssl/pem.h>
10 #include <openssl/err.h>
11
12 -#define STATUS_FILE_VERSION 12
12 +#define STATUS_FILE_VERSION 13
13
14 #define STATUS_FILENAME "status-netdata.json"
15
src/daemon/pulse/pulse-aral.c
+12 -9
@@ -33,6 +33,17 @@ void pulse_aral_register_statistics(struct aral_statistics *stats, const char *n
33 spinlock_unlock(&globals.spinlock);
34 }
35
36 +void pulse_aral_unregister_statistics(struct aral_statistics *stats) {
37 + spinlock_lock(&globals.spinlock);
38 + struct aral_info *ai = ARAL_STATS_GET(&globals.idx, (Word_t)stats);
39 + if(ai) {
40 + ARAL_STATS_DEL(&globals.idx, (Word_t)stats);
41 + freez((void *)ai->name);
42 + freez(ai);
43 + }
44 + spinlock_unlock(&globals.spinlock);
45 +}
46 +
47 void pulse_aral_register(ARAL *ar, const char *name) {
48 if(!ar) return;
49
@@ -47,15 +58,7 @@ void pulse_aral_register(ARAL *ar, const char *name) {
58 void pulse_aral_unregister(ARAL *ar) {
59 if(!ar) return;
60 struct aral_statistics *stats = aral_get_statistics(ar);
50 -
51 - spinlock_lock(&globals.spinlock);
52 - struct aral_info *ai = ARAL_STATS_GET(&globals.idx, (Word_t)stats);
53 - if(ai) {
54 - ARAL_STATS_DEL(&globals.idx, (Word_t)stats);
55 - freez((void *)ai->name);
56 - freez(ai);
57 - }
58 - spinlock_unlock(&globals.spinlock);
61 + pulse_aral_unregister_statistics(stats);
62 }
63
64 void pulse_aral_init(void) {
src/daemon/pulse/pulse-aral.h
+2
@@ -6,6 +6,8 @@
6 #include "daemon/common.h"
7
8 void pulse_aral_register_statistics(struct aral_statistics *stats, const char *name);
9 +void pulse_aral_unregister_statistics(struct aral_statistics *stats);
10 +
11 void pulse_aral_register(ARAL *ar, const char *name);
12 void pulse_aral_unregister(ARAL *ar);
13
src/database/engine/metric.c
+67 -6
@@ -357,14 +357,75 @@ struct aral_statistics *mrg_aral_stats(void) {
357 return &mrg_aral_statistics;
358 }
359
360 -ALWAYS_INLINE void mrg_destroy(MRG *mrg __maybe_unused) {
361 - // no destruction possible
362 - // we can't traverse the metrics list
360 +size_t mrg_destroy(MRG *mrg) {
361 + if (unlikely(!mrg))
362 + return 0;
363
364 - // to delete entries, the caller needs to keep pointers to them
365 - // and delete them one by one
364 + size_t referenced = 0;
365
367 - pulse_aral_unregister(mrg->index[0].aral);
366 + // Traverse all partitions
367 + for (size_t partition = 0; partition < UUIDMAP_PARTITIONS; partition++) {
368 + // Lock the partition to prevent new entries while we're cleaning up
369 + mrg_index_write_lock(mrg, partition);
370 +
371 + Pvoid_t uuid_judy = mrg->index[partition].uuid_judy;
372 + Word_t uuid_index = 0;
373 + Pvoid_t *uuid_pvalue;
374 +
375 + // Traverse all UUIDs in this partition
376 + for (uuid_pvalue = JudyLFirst(uuid_judy, &uuid_index, PJE0);
377 + uuid_pvalue != NULL && uuid_pvalue != PJERR;
378 + uuid_pvalue = JudyLNext(uuid_judy, &uuid_index, PJE0)) {
379 +
380 + if (!(*uuid_pvalue))
381 + continue;
382 +
383 + // Get the sections judy for this UUID
384 + Pvoid_t sections_judy = *uuid_pvalue;
385 + Word_t section_index = 0;
386 + Pvoid_t *section_pvalue;
387 +
388 + // Traverse all sections for this UUID
389 + for (section_pvalue = JudyLFirst(sections_judy, &section_index, PJE0);
390 + section_pvalue != NULL && section_pvalue != PJERR;
391 + section_pvalue = JudyLNext(sections_judy, &section_index, PJE0)) {
392 +
393 + if (!(*section_pvalue))
394 + continue;
395 +
396 + METRIC *metric = *section_pvalue;
397 +
398 + // Try to acquire metric for deletion
399 + if (!refcount_acquire_for_deletion(&metric->refcount))
400 + referenced++;
401 +
402 + uuidmap_free(metric->uuid);
403 + aral_freez(mrg->index[partition].aral, metric);
404 + MRG_STATS_DELETED_METRIC(mrg, partition);
405 + }
406 +
407 + JudyLFreeArray(&sections_judy, PJE0);
408 + }
409 +
410 + JudyLFreeArray(&uuid_judy, PJE0);
411 +
412 + // Update the main Judy array reference
413 + mrg->index[partition].uuid_judy = uuid_judy;
414 +
415 + // Unlock the partition
416 + mrg_index_write_unlock(mrg, partition);
417 +
418 + // Destroy the aral for this partition
419 + aral_destroy(mrg->index[partition].aral);
420 + }
421 +
422 + // Unregister the aral statistics
423 + pulse_aral_unregister_statistics(&mrg_aral_statistics);
424 +
425 + // Free the MRG structure
426 + freez(mrg);
427 +
428 + return referenced;
429 }
430
431 ALWAYS_INLINE METRIC *mrg_metric_add_and_acquire(MRG *mrg, MRG_ENTRY entry, bool *ret) {
src/database/engine/metric.h
+3 -1
@@ -41,7 +41,9 @@ struct mrg_statistics {
41 };
42
43 MRG *mrg_create(void);
44 -void mrg_destroy(MRG *mrg);
44 +
45 +// returns the number of metrics that were freed, but were still referenced
46 +size_t mrg_destroy(MRG *mrg);
47
48 METRIC *mrg_metric_dup(MRG *mrg, METRIC *metric);
49 void mrg_metric_release(MRG *mrg, METRIC *metric);
src/database/rrdfunctions-inline.c
+3 -10
@@ -2,22 +2,18 @@
2
3 #include "rrdfunctions-inline.h"
4
5 -struct rrd_function_inline {
6 - rrd_function_execute_inline_cb_t cb;
7 -};
8 -
5 static int rrd_function_run_inline(struct rrd_function_execute *rfe, void *data) {
6
7 // IMPORTANT: this function MUST call the result_cb even on failures
8
13 - struct rrd_function_inline *fi = data;
9 + rrd_function_execute_inline_cb_t execute_cb = data;
10
11 int code;
12
13 if(rfe->is_cancelled.cb && rfe->is_cancelled.cb(rfe->is_cancelled.data))
14 code = HTTP_RESP_CLIENT_CLOSED_REQUEST;
15 else
20 - code = fi->cb(rfe->result.wb, rfe->function, rfe->payload, rfe->source);
16 + code = execute_cb(rfe->result.wb, rfe->function, rfe->payload, rfe->source);
17
18 if(code == HTTP_RESP_CLIENT_CLOSED_REQUEST || (rfe->is_cancelled.cb && rfe->is_cancelled.cb(rfe->is_cancelled.data))) {
19 buffer_flush(rfe->result.wb);
@@ -36,10 +32,7 @@ void rrd_function_add_inline(RRDHOST *host, RRDSET *st, const char *name, int ti
32
33 rrd_collector_started(); // this creates a collector that runs for as long as netdata runs
34
39 - struct rrd_function_inline *fi = callocz(1, sizeof(struct rrd_function_inline));
40 - fi->cb = execute_cb;
41 -
35 rrd_function_add(host, st, name, timeout, priority, version,
36 help, tags, access, true,
44 - rrd_function_run_inline, fi);
37 + rrd_function_run_inline, execute_cb);
38 }
src/database/rrdhost.c
+12 -2
@@ -132,12 +132,14 @@ static inline RRDHOST *rrdhost_index_add_hostname(RRDHOST *host) {
132 if(ret_hostname == host)
133 rrdhost_option_set(host, RRDHOST_OPTION_INDEXED_HOSTNAME);
134 else {
135 - //have the same hostname but it's not the same host
136 - //keep the new one only if the old one is orphan or archived
135 + // have the same hostname, but it's not the same host
136 + // keep the new one only if the old one is orphan or archived
137 if (rrdhost_flag_check(ret_hostname, RRDHOST_FLAG_ORPHAN) || rrdhost_flag_check(ret_hostname, RRDHOST_FLAG_ARCHIVED)) {
138 rrdhost_index_del_hostname(ret_hostname);
139 rrdhost_index_add_hostname(host);
140 }
141 + else
142 + rrdhost_option_clear(host, RRDHOST_OPTION_INDEXED_HOSTNAME);
143 }
144
145 return host;
@@ -838,6 +840,7 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host) {
840
841 rrdhost_destroy_rrdcontexts(host);
842 rrdlabels_destroy(host->rrdlabels);
843 + destroy_aclk_config(host);
844
845 string_freez(host->hostname);
846 string_freez(host->os);
@@ -863,5 +866,12 @@ void rrdhost_free_all(void) {
866 if(localhost)
867 rrdhost_free___while_having_rrd_wrlock(localhost);
868
869 + localhost = NULL;
870 +
871 + dictionary_destroy(rrdhost_root_index_hostname);
872 + dictionary_destroy(rrdhost_root_index);
873 + rrdhost_root_index_hostname = NULL;
874 + rrdhost_root_index = NULL;
875 +
876 rrd_wrunlock();
877 }
src/database/sqlite/sqlite_aclk.c
+9
@@ -909,6 +909,15 @@ void create_aclk_config(RRDHOST *host __maybe_unused, nd_uuid_t *host_uuid __may
909 wc->node_info_send_time = (host == localhost || NULL == localhost) ? now - 25 : now;
910 }
911
912 +void destroy_aclk_config(RRDHOST *host)
913 +{
914 + if (!host || !host->aclk_config)
915 + return;
916 +
917 + freez(host->aclk_config);
918 + host->aclk_config = NULL;
919 +}
920 +
921 #define SQL_FETCH_ALL_HOSTS \
922 "SELECT host_id, hostname, registry_hostname, update_every, os, " \
923 "timezone, hops, memory_mode, abbrev_timezone, utc_offset, program_name, " \
src/database/sqlite/sqlite_aclk.h
+1 -1
@@ -50,10 +50,10 @@ typedef struct aclk_sync_cfg_t {
50 time_t node_info_send_time;
51 time_t node_collectors_send;
52 char node_id[UUID_STR_LEN];
53 - char *alerts_snapshot_uuid; // will contain the snapshot_uuid value if snapshot was requested
53 } aclk_sync_cfg_t;
54
55 void create_aclk_config(RRDHOST *host, nd_uuid_t *host_uuid, nd_uuid_t *node_id);
56 +void destroy_aclk_config(RRDHOST *host);
57 void sql_aclk_sync_init(void);
58 void aclk_push_alert_config(const char *node_id, const char *config_hash);
59 void schedule_node_state_update(RRDHOST *host, uint64_t delay);
src/libnetdata/dictionary/dictionary.c
+5 -2
@@ -330,11 +330,11 @@ static void dictionary_queue_for_destruction(DICTIONARY *dict) {
330 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
331 }
332
333 -void cleanup_destroyed_dictionaries(void) {
333 +bool cleanup_destroyed_dictionaries(void) {
334 netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
335 if (!dictionaries_waiting_to_be_destroyed) {
336 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
337 - return;
337 + return false;
338 }
339
340 DICTIONARY *dict, *last = NULL, *next = NULL;
@@ -371,7 +371,10 @@ void cleanup_destroyed_dictionaries(void) {
371 }
372 }
373
374 + bool ret = dictionaries_waiting_to_be_destroyed != NULL;
375 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
376 +
377 + return ret;
378 }
379
380 // ----------------------------------------------------------------------------
src/libnetdata/dictionary/dictionary.h
+1 -1
@@ -168,7 +168,7 @@ void dictionary_version_increment(DICTIONARY *dict);
168
169 void dictionary_garbage_collect(DICTIONARY *dict);
170
171 -void cleanup_destroyed_dictionaries(void);
171 +bool cleanup_destroyed_dictionaries(void);
172
173 // ----------------------------------------------------------------------------
174 // Set an item in the dictionary
src/libnetdata/threads/threads.c
+1
@@ -421,6 +421,7 @@ void nd_thread_signal_cancel(ND_THREAD *nti) {
421 spinlock_unlock(&nti->canceller.spinlock);
422 }
423
424 +ALWAYS_INLINE
425 bool nd_thread_signaled_to_cancel(void) {
426 if(!_nd_thread_info) return false;
427 return __atomic_load_n(&_nd_thread_info->cancel_atomic, __ATOMIC_RELAXED);
src/libnetdata/uuid/uuidmap.c
+53
@@ -278,6 +278,59 @@ UUIDMAP_ID uuidmap_dup(UUIDMAP_ID id) {
278 return id;
279 }
280
281 +size_t uuidmap_destroy(void) {
282 + size_t referenced = 0;
283 +
284 + // Traverse all partitions
285 + for (size_t partition = 0; partition < UUIDMAP_PARTITIONS; partition++) {
286 + // Lock the partition to prevent new entries while we're cleaning up
287 + rw_spinlock_write_lock(&uuid_map.p[partition].spinlock);
288 +
289 + Pvoid_t uuid_to_id = uuid_map.p[partition].uuid_to_id;
290 + Pvoid_t id_to_uuid = uuid_map.p[partition].id_to_uuid;
291 + Pvoid_t freed_ids = uuid_map.p[partition].freed_ids;
292 +
293 + // Process all entries in the id_to_uuid map
294 + Word_t id_index = 0;
295 + Pvoid_t *id_pvalue;
296 +
297 + for (id_pvalue = JudyLFirst(id_to_uuid, &id_index, PJE0);
298 + id_pvalue != NULL && id_pvalue != PJERR;
299 + id_pvalue = JudyLNext(id_to_uuid, &id_index, PJE0)) {
300 +
301 + if (!(*id_pvalue))
302 + continue;
303 +
304 + struct uuidmap_entry *ue = *id_pvalue;
305 +
306 + // Try to acquire for deletion
307 + if (!refcount_acquire_for_deletion(&ue->refcount))
308 + referenced++;
309 +
310 + aral_freez(uuid_map.ar, ue);
311 + }
312 +
313 + // Free all Judy arrays
314 + JudyHSFreeArray(&uuid_to_id, PJE0);
315 + JudyLFreeArray(&id_to_uuid, PJE0);
316 + JudyLFreeArray(&freed_ids, PJE0);
317 +
318 + // Reset partition data
319 + memset(&uuid_map.p[partition], 0, sizeof(uuid_map.p[partition]));
320 +
321 + rw_spinlock_write_unlock(&uuid_map.p[partition].spinlock);
322 + }
323 +
324 + // Destroy ARAL
325 + if (uuid_map.ar) {
326 + aral_destroy(uuid_map.ar);
327 + uuid_map.ar = NULL;
328 + }
329 +
330 + memset(&uuid_map, 0, sizeof(uuid_map));
331 + return referenced;
332 +}
333 +
334 // --------------------------------------------------------------------------------------------------------------------
335
336 static volatile bool stop_flag = false;
src/libnetdata/uuid/uuidmap.h
+3
@@ -24,6 +24,9 @@ static inline UUIDMAP_ID uuidmap_make_id(uint8_t partition, uint32_t id) {
24 // returns ID, or zero on error
25 UUIDMAP_ID uuidmap_create(const nd_uuid_t uuid);
26
27 +// returns the number of entries still referenced (although freed)
28 +size_t uuidmap_destroy(void);
29 +
30 // delete a uuid from the map
31 void uuidmap_free(UUIDMAP_ID id);
32
src/streaming/stream-connector.c
+22 -1
@@ -531,7 +531,7 @@ static void *stream_connector_thread(void *ptr) {
531 worker_register_job_custom_metric(WORKER_SENDER_CONNECTOR_JOB_CANCELLED_NODES, "cancelled nodes", "nodes", WORKER_METRIC_ABSOLUTE);
532
533 unsigned job_id = 0;
534 - while(!nd_thread_signaled_to_cancel() && service_running(SERVICE_STREAMING)) {
534 + while(service_running(SERVICE_STREAMING)) {
535
536 worker_is_idle();
537 job_id = completion_wait_for_a_job_with_timeout(&sc->completion, job_id, 1000);
@@ -619,6 +619,27 @@ static void *stream_connector_thread(void *ptr) {
619 worker_set_metric(WORKER_SENDER_CONNECTOR_JOB_CANCELLED_NODES, (NETDATA_DOUBLE)cancelled_nodes);
620 }
621
622 +#if defined(FSANITIZE_ADDRESS)
623 + // sometimes this thread exits, with localhost still in the queue
624 + sleep(3);
625 +#endif
626 +
627 + spinlock_lock(&sc->queue.spinlock);
628 + Word_t idx = 0;
629 + for(struct sender_state *s = SENDERS_FIRST(&sc->queue.senders, &idx);
630 + s;
631 + s = SENDERS_NEXT(&sc->queue.senders, &idx)) {
632 + SENDERS_DEL(&sc->queue.senders, idx);
633 + spinlock_unlock(&sc->queue.spinlock);
634 +
635 + // do not have the connector lock when calling these
636 + stream_sender_on_disconnect(s);
637 + stream_sender_remove(s, s->exit.reason);
638 +
639 + spinlock_lock(&sc->queue.spinlock);
640 + }
641 + spinlock_unlock(&sc->queue.spinlock);
642 +
643 return NULL;
644 }
645
src/streaming/stream-replication-sender.c
-1
@@ -1718,7 +1718,6 @@ void *replication_thread_main(void *ptr) {
1718 for(size_t i = 0; i < threads ;i++) {
1719 char tag[NETDATA_THREAD_TAG_MAX + 1];
1720 snprintfz(tag, NETDATA_THREAD_TAG_MAX, "REPLAY[%zu]", i + 2);
1721 - replication_globals.main_thread.threads_ptrs[i] = mallocz(sizeof(ND_THREAD *));
1721 __atomic_add_fetch(&replication_buffers_allocated, sizeof(ND_THREAD *), __ATOMIC_RELAXED);
1722 replication_globals.main_thread.threads_ptrs[i] = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE,
1723 replication_worker_thread, NULL);
src/streaming/stream-sender.c
+1 -3
@@ -367,9 +367,7 @@ void stream_sender_remove(struct sender_state *s, STREAM_HANDSHAKE reason) {
367 s->exit.reason = 0;
368
369 __atomic_store_n(&s->exit.shutdown, false, __ATOMIC_RELAXED);
370 - rrdhost_flag_clear(s->host,
371 - RRDHOST_FLAG_STREAM_SENDER_ADDED | RRDHOST_FLAG_STREAM_SENDER_CONNECTED |
372 - RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS);
370 + rrdhost_flag_clear(s->host, RRDHOST_FLAG_STREAM_SENDER_ADDED | RRDHOST_FLAG_STREAM_SENDER_CONNECTED | RRDHOST_FLAG_STREAM_SENDER_READY_4_METRICS);
371
372 s->last_state_since_t = now_realtime_sec();
373 stream_parent_set_host_disconnect_reason(s->host, reason, s->last_state_since_t);