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, §ion_index, PJE0);
390
+ section_pvalue != NULL && section_pvalue != PJERR;
391
+ section_pvalue = JudyLNext(sections_judy, §ion_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(§ions_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);