@cryptotaxi247 / netdata / commits / 101a1e673

Fix SIGSEGV in ctx_hosts_load during shutdown (#22419)

Fix SIGSEGV in ctx_hosts_load during shutdown The libuv worker running ctx_hosts_load only checked for shutdown between hosts, so a host with many dimensions kept the SQL row loops busy past the metadata thread's 15s wait. The daemon then ran sqlite3_shutdown() while the worker was still alive, and the worker's later sqlite3_close_v2() crashed in pcache1EnforceMaxPage on freed pcache1 globals. - Check exit_initiated_get() inside ctx_get_chart_list / ctx_get_dimension_list / ctx_get_context_list row loops, between the three list calls in rrdhost_load_rrdcontext_data, inside its outer trigger loop, and at the top of restore_host_context. - Route every per-thread sqlite3_close_v2 in the worker through a new sql_close_thread_db_safe() that takes sqlite_spinlock and skips the close once sqlite_library_initialized is false, serializing with sqlite_library_shutdown().

Stelios Fragkakis committed May 7, 2026 at 17:07 UTC 101a1e673c8a309c188e315ec6dade329d25b191
5 files changed +51 -19
src/database/contexts/rrdcontext-loading.c
+11
@@ -156,8 +156,16 @@ void rrdhost_load_rrdcontext_data(RRDHOST *host) {
156 th_ignored_metrics = th_ignored_instances = th_zero_retention_metrics = 0;
157
158 ctx_get_context_list(&host->host_id.uuid, rrdcontext_load_context_callback, host);
159 + if (unlikely(exit_initiated_get()))
160 + return;
161 +
162 ctx_get_chart_list(&host->host_id.uuid, rrdinstance_load_instance_callback, host);
163 + if (unlikely(exit_initiated_get()))
164 + return;
165 +
166 ctx_get_dimension_list(&host->host_id.uuid, rrdinstance_load_dimension_callback, host);
167 + if (unlikely(exit_initiated_get()))
168 + return;
169
170 size_t ignored_metrics = th_ignored_metrics, ignored_instances = th_ignored_instances, zero_retention_metrics = th_zero_retention_metrics;
171 size_t loaded_metrics = 0, loaded_instances = 0, loaded_contexts = 0;
@@ -165,6 +173,9 @@ void rrdhost_load_rrdcontext_data(RRDHOST *host) {
173
174 RRDCONTEXT *rc;
175 dfe_start_read(host->rrdctx.contexts, rc) {
176 + if (unlikely(exit_initiated_get()))
177 + break;
178 +
179 size_t instances = 0;
180
181 RRDINSTANCE *ri;
src/database/sqlite/sqlite_context.c
+9
@@ -104,6 +104,9 @@ void ctx_get_chart_list(nd_uuid_t *host_uuid, void (*dict_cb)(SQL_CHART_DATA *,
104 param = 0;
105 SQL_CHART_DATA chart_data = { 0 };
106 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
107 + if (unlikely(exit_initiated_get()))
108 + break;
109 +
110 if (unlikely(!sqlite3_column_uuid_copy(res, 0, chart_data.chart_id))) {
111 error_report("CTX [%s]: Got invalid chart id in column 0. Ignoring it.", host_guid);
112 continue;
@@ -146,6 +149,9 @@ void ctx_get_dimension_list(nd_uuid_t *host_uuid, void (*dict_cb)(SQL_DIMENSION_
149
150 param = 0;
151 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
152 + if (unlikely(exit_initiated_get()))
153 + break;
154 +
155 if (unlikely(!sqlite3_column_uuid_copy(res, 0, dimension_data.dim_id))) {
156 error_report("CTX [%s]: Got invalid dimension id in column 0. Ignoring it.", host_guid);
157 continue;
@@ -213,6 +219,9 @@ void ctx_get_context_list(nd_uuid_t *host_uuid, void (*dict_cb)(VERSIONED_CONTEX
219 param = 0;
220
221 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
222 + if (unlikely(exit_initiated_get()))
223 + break;
224 +
225 context_data.id = (char *) sqlite3_column_text(res, 0);
226 context_data.version = sqlite3_column_int64(res, 1);
227 context_data.title = (char *) sqlite3_column_text(res, 2);
src/database/sqlite/sqlite_functions.c
+16
@@ -455,6 +455,22 @@ void sql_close_database(sqlite3 *database, const char *database_name)
455
456 extern sqlite3 *db_context_meta;
457
458 +// Close a thread-local sqlite3 handle while serializing against sqlite_library_shutdown().
459 +// If the SQLite library is no longer initialized, the handle is leaked deliberately; the OS
460 +// will reclaim it at process exit, which is strictly safer than crashing inside pcache1.
461 +void sql_close_thread_db_safe(sqlite3 **database)
462 +{
463 + if (unlikely(!database || !*database))
464 + return;
465 +
466 + spinlock_lock(&sqlite_spinlock);
467 + if (sqlite_library_initialized)
468 + (void) sqlite3_close_v2(*database);
469 + spinlock_unlock(&sqlite_spinlock);
470 +
471 + *database = NULL;
472 +}
473 +
474 void sqlite_close_databases(void)
475 {
476 // In case we have statements in the main thread (we should not)
src/database/sqlite/sqlite_functions.h
+1
@@ -166,6 +166,7 @@ int sqlite_library_init(void);
166 void sqlite_library_shutdown(void);
167
168 void sql_close_database(sqlite3 *database, const char *database_name);
169 +void sql_close_thread_db_safe(sqlite3 **database);
170 void sqlite_close_databases(void);
171 uint64_t get_total_database_space(void);
172 int sqlite_release_memory(int bytes);
src/database/sqlite/sqlite_metadata.c
+14 -19
@@ -1895,6 +1895,11 @@ static void restore_host_context(void *arg)
1895 if (!host)
1896 return;
1897
1898 + if (unlikely(exit_initiated_get())) {
1899 + __atomic_store_n(&hclt->finished, true, __ATOMIC_RELEASE);
1900 + return;
1901 + }
1902 +
1903 if (!db_meta_thread) {
1904 if (hclt->db_meta_thread) {
1905 db_meta_thread = hclt->db_meta_thread;
@@ -1903,17 +1908,13 @@ static void restore_host_context(void *arg)
1908 char sqlite_database[FILENAME_MAX + 1];
1909 snprintfz(sqlite_database, sizeof(sqlite_database) - 1, "%s/netdata-meta.db", netdata_configured_cache_dir);
1910 int rc = sqlite3_open_v2(sqlite_database, &db_meta_thread, SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, NULL);
1906 - if (rc != SQLITE_OK) {
1907 - sqlite3_close_v2(db_meta_thread);
1908 - db_meta_thread = NULL;
1909 - }
1911 + if (rc != SQLITE_OK)
1912 + sql_close_thread_db_safe(&db_meta_thread);
1913
1914 snprintfz(sqlite_database, sizeof(sqlite_database) - 1, "%s/context-meta.db", netdata_configured_cache_dir);
1915 rc = sqlite3_open_v2(sqlite_database, &db_context_thread, SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, NULL);
1913 - if (rc != SQLITE_OK) {
1914 - sqlite3_close_v2(db_context_thread);
1915 - db_context_thread = NULL;
1916 - }
1916 + if (rc != SQLITE_OK)
1917 + sql_close_thread_db_safe(&db_context_thread);
1918
1919 hclt->db_meta_thread = db_meta_thread;
1920 hclt->db_context_thread = db_context_thread;
@@ -2069,11 +2070,8 @@ static void ctx_hosts_load(uv_work_t *req)
2070
2071 if (should_clean_threads) {
2072 for (size_t index = 0; index < max_threads; index++) {
2072 - if (hclt[index].db_meta_thread)
2073 - sqlite3_close_v2(hclt[index].db_meta_thread);
2074 -
2075 - if (hclt[index].db_context_thread)
2076 - sqlite3_close_v2(hclt[index].db_context_thread);
2073 + sql_close_thread_db_safe(&hclt[index].db_meta_thread);
2074 + sql_close_thread_db_safe(&hclt[index].db_context_thread);
2075 }
2076 }
2077
@@ -2090,12 +2088,9 @@ static void ctx_hosts_load(uv_work_t *req)
2088 sync_exec,
2089 load_duration);
2090
2093 - if (db_meta_thread) {
2094 - sqlite3_close_v2(db_meta_thread);
2095 - sqlite3_close_v2(db_context_thread);
2096 - db_meta_thread = NULL;
2097 - db_context_thread = NULL;
2098 - }
2091 + sql_close_thread_db_safe(&db_meta_thread);
2092 + sql_close_thread_db_safe(&db_context_thread);
2093 +
2094 freez(hclt);
2095 worker_is_idle();
2096 }