master
c 458 lines 16 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "daemon-shutdown.h"
4 #include "daemon-service.h"
5 #include "status-file.h"
6 #include "daemon/daemon-shutdown-watcher.h"
7 #include "static_threads.h"
8 #include "common.h"
9
10 #include <curl/curl.h>
11
12 #ifdef ENABLE_SENTRY
13 #include "sentry-native/sentry-native.h"
14 #endif
15
16 // External configuration structures that need cleanup
17 extern struct config netdata_config;
18 extern struct config cloud_config;
19
20 // Functions to free various configurations
21 void claim_config_free(void);
22 void rrd_functions_inflight_destroy(void);
23 void cgroup_netdev_link_destroy(void);
24 void bearer_tokens_destroy(void);
25 void alerts_by_x_cleanup(void);
26 void websocket_threads_join(void);
27 void mcp_functions_registry_cleanup(void);
28
29 static bool abort_on_fatal = true;
30
31 void abort_on_fatal_disable(void) {
32 abort_on_fatal = false;
33 }
34
35 void abort_on_fatal_enable(void) {
36 abort_on_fatal = true;
37 }
38
39 #ifdef ENABLE_SENTRY
40 NEVER_INLINE
41 static bool shutdown_on_fatal(void) {
42 // keep this as a separate function, to have it logged like this in sentry
43 if(abort_on_fatal)
44 abort();
45 else
46 return false;
47 }
48 #endif
49
50 void web_client_cache_destroy(void);
51
52 extern struct netdata_static_thread *static_threads;
53
54 void netdata_log_exit_reason(void) {
55 CLEAN_BUFFER *wb = buffer_create(0, NULL);
56 EXIT_REASON_2buffer(wb, exit_initiated_get(), ", ");
57
58 ND_LOG_STACK lgs[] = {
59 ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &netdata_exit_msgid),
60 ND_LOG_FIELD_END(),
61 };
62 ND_LOG_STACK_PUSH(lgs);
63
64 nd_log(NDLS_DAEMON, is_exit_reason_normal(exit_initiated_get()) ? NDLP_NOTICE : NDLP_CRIT,
65 "NETDATA SHUTDOWN: initializing shutdown with code due to: %s",
66 buffer_tostring(wb));
67 }
68
69 void cancel_main_threads(void) {
70 nd_log_limits_unlimited();
71
72 if (!static_threads)
73 return;
74
75 int i;
76 for (i = 0; static_threads[i].name != NULL ; i++) {
77 if (static_threads[i].enabled == NETDATA_MAIN_THREAD_RUNNING) {
78 if (static_threads[i].thread) {
79 netdata_log_info("EXIT: Stopping main thread: %s", static_threads[i].name);
80 nd_thread_signal_cancel(static_threads[i].thread);
81 } else {
82 netdata_log_info("EXIT: No thread running (marking as EXITED): %s", static_threads[i].name);
83 static_threads[i].enabled = NETDATA_MAIN_THREAD_EXITED;
84 }
85 }
86 }
87
88 for (i = 0; static_threads[i].name != NULL ; i++) {
89 if(static_threads[i].thread && !nd_thread_is_me(static_threads[i].thread)) {
90 if (static_threads[i].enabled == NETDATA_MAIN_THREAD_EXITED)
91 nd_thread_join(static_threads[i].thread);
92 }
93 }
94 netdata_log_info("All threads finished.");
95
96 freez(static_threads);
97 static_threads = NULL;
98 }
99
100 #ifdef ENABLE_DBENGINE
101 static void rrdeng_exit_background(void *ptr) {
102 struct rrdengine_instance *ctx = ptr;
103 rrdeng_exit(ctx);
104 }
105
106 static void rrdeng_quiesce_all()
107 {
108 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++)
109 rrdeng_quiesce(multidb_ctx[tier]);
110 }
111
112 static void rrdeng_flush_everything_and_wait(bool wait_flush, bool wait_collectors, bool dirty_only) {
113 static size_t starting_size_to_flush = 0;
114
115 if(!pgc_hot_and_dirty_entries(main_cache))
116 return;
117
118 nd_log(NDLS_DAEMON, NDLP_INFO, "Flushing DBENGINE %s dirty pages...", dirty_only ? "only" : "hot &");
119 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
120 if (dirty_only)
121 rrdeng_flush_dirty(multidb_ctx[tier]);
122 else
123 rrdeng_flush_all(multidb_ctx[tier]);
124 }
125
126 struct pgc_statistics pgc_main_stats = pgc_get_statistics(main_cache);
127 size_t size_to_flush = pgc_main_stats.queues[PGC_QUEUE_HOT].size + pgc_main_stats.queues[PGC_QUEUE_DIRTY].size;
128 size_t entries_to_flush = pgc_main_stats.queues[PGC_QUEUE_HOT].entries + pgc_main_stats.queues[PGC_QUEUE_DIRTY].entries;
129 if(size_to_flush > starting_size_to_flush || !starting_size_to_flush)
130 starting_size_to_flush = size_to_flush;
131
132 if(wait_collectors) {
133 size_t running = 1;
134 size_t count = 50;
135 while (running && count) {
136 running = 0;
137 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++)
138 running += rrdeng_collectors_running(multidb_ctx[tier]);
139
140 if (running) {
141 nd_log_limit_static_thread_var(erl, 1, 100 * USEC_PER_MS);
142 nd_log_limit(&erl, NDLS_DAEMON, NDLP_NOTICE, "waiting for %zu collectors to finish", running);
143 }
144 count--;
145 }
146 }
147
148 if(!wait_flush)
149 return;
150
151 for(size_t iterations = 0; true ;iterations++) {
152 pgc_main_stats = pgc_get_statistics(main_cache);
153 size_to_flush = pgc_main_stats.queues[PGC_QUEUE_HOT].size + pgc_main_stats.queues[PGC_QUEUE_DIRTY].size;
154 entries_to_flush = pgc_main_stats.queues[PGC_QUEUE_HOT].entries + pgc_main_stats.queues[PGC_QUEUE_DIRTY].entries;
155 if(!starting_size_to_flush || size_to_flush > starting_size_to_flush)
156 starting_size_to_flush = size_to_flush;
157
158 if(!size_to_flush || !entries_to_flush)
159 break;
160
161 size_t flushed = starting_size_to_flush - size_to_flush;
162
163 if(iterations % 10 == 0) {
164 char hot[64], dirty[64];
165 size_snprintf(hot, sizeof(hot), pgc_main_stats.queues[PGC_QUEUE_HOT].size, "B", false);
166 size_snprintf(dirty, sizeof(hot), pgc_main_stats.queues[PGC_QUEUE_DIRTY].size, "B", false);
167
168 nd_log(NDLS_DAEMON, NDLP_INFO, "DBENGINE: flushing at %.2f%% { hot: %s, dirty: %s }...",
169 (double)flushed * 100.0 / (double)starting_size_to_flush,
170 hot, dirty);
171 }
172 sleep_usec(100 * USEC_PER_MS);
173 }
174 nd_log(NDLS_DAEMON, NDLP_INFO, "DBENGINE: flushing completed!");
175 }
176 #endif
177
178 static void netdata_cleanup_and_exit(EXIT_REASON reason, bool abnormal, bool exit_when_done) {
179 exit_initiated_set(reason);
180
181 // don't recurse (due to a fatal, while exiting)
182 static bool run = false;
183 if(run) {
184 nd_log(NDLS_DAEMON, NDLP_ERR, "EXIT: Recursion detected. Exiting immediately.");
185 exit(1);
186 }
187 run = true;
188 daemon_status_file_update_status(DAEMON_STATUS_EXITING);
189
190 nd_log_limits_unlimited();
191 netdata_log_exit_reason();
192
193 watcher_thread_start();
194 usec_t shutdown_start_time = now_monotonic_usec();
195 watcher_shutdown_begin();
196
197 #ifdef ENABLE_DBENGINE
198 if(!abnormal && dbengine_enabled) {
199 rrdeng_quiesce_all();
200 rrdeng_flush_everything_and_wait(false, false, true);
201 }
202 #endif
203
204 webrtc_close_all_connections();
205 watcher_step_complete(WATCHER_STEP_ID_CLOSE_WEBRTC_CONNECTIONS);
206
207 service_signal_exit(ABILITY_WEB_REQUESTS | SERVICE_ACLK | ABILITY_STREAMING_CONNECTIONS | SERVICE_SYSTEMD);
208
209 service_signal_exit(SERVICE_EXPORTERS | SERVICE_HEALTH | SERVICE_WEB_SERVER | SERVICE_HTTPD);
210
211 watcher_step_complete(WATCHER_STEP_ID_DISABLE_MAINTENANCE_NEW_QUERIES_NEW_WEB_REQUESTS_NEW_STREAMING_CONNECTIONS);
212
213 service_wait_exit(SERVICE_SYSTEMD, 5 * USEC_PER_SEC);
214 watcher_step_complete(WATCHER_STEP_ID_STOP_MAINTENANCE_THREAD);
215
216 service_wait_exit(SERVICE_EXPORTERS | SERVICE_HEALTH | SERVICE_WEB_SERVER | SERVICE_HTTPD, 3 * USEC_PER_SEC);
217 watcher_step_complete(WATCHER_STEP_ID_STOP_EXPORTERS_HEALTH_AND_WEB_SERVERS_THREADS);
218
219 // Drain websocket threads while data substrates are still alive. Websocket
220 // message dispatch (e.g. MCP) is synchronous on the websocket thread; an
221 // in-flight handler will not let the loop observe its cancel flag until it
222 // returns. If we wait until after dbengine/metasync/workers are gone, an
223 // MCP request that needs them never returns and the watchdog aborts.
224 websocket_threads_join();
225 watcher_step_complete(WATCHER_STEP_ID_STOP_WEBSOCKET_THREADS);
226
227 stream_threads_cancel();
228 service_wait_exit(SERVICE_COLLECTORS | SERVICE_STREAMING, 20 * USEC_PER_SEC);
229 service_signal_exit(SERVICE_STREAMING_CONNECTOR);
230 watcher_step_complete(WATCHER_STEP_ID_STOP_COLLECTORS_AND_STREAMING_THREADS);
231
232 #ifdef ENABLE_DBENGINE
233 if(!abnormal && dbengine_enabled)
234 // flush all dirty pages now that all collectors and streaming completed
235 rrdeng_flush_everything_and_wait(false, false, true);
236 #endif
237
238 service_wait_exit(SERVICE_REPLICATION, 5 * USEC_PER_SEC);
239 watcher_step_complete(WATCHER_STEP_ID_STOP_REPLICATION_THREADS);
240
241 ml_stop_threads();
242 // ml_fini() (which closes ml_db) is deferred until after
243 // metadata_sync_shutdown() drains the metasync workers below. Those
244 // workers call ml_dimension_load_models() which uses ml_db; closing it
245 // here exposes the metasync worker to a use-after-free on the SQLite
246 // handle and triggers SIGSEGV inside sqlite3_prepare_v2 -> findElementWithHash.
247 watcher_step_complete(WATCHER_STEP_ID_DISABLE_ML_DETEC_AND_TRAIN_THREADS);
248
249 service_wait_exit(SERVICE_CONTEXT, 5 * USEC_PER_SEC);
250 watcher_step_complete(WATCHER_STEP_ID_STOP_CONTEXT_THREAD);
251
252 web_client_cache_destroy();
253 watcher_step_complete(WATCHER_STEP_ID_CLEAR_WEB_CLIENT_CACHE);
254
255 aclk_synchronization_shutdown();
256 watcher_step_complete(WATCHER_STEP_ID_STOP_ACLK_SYNC_THREAD);
257
258 service_signal_exit(SERVICE_ACLK);
259
260 service_wait_exit(SERVICE_ACLK, 3 * USEC_PER_SEC);
261 watcher_step_complete(WATCHER_STEP_ID_STOP_ACLK_MQTT_THREAD);
262
263 service_wait_exit(~0, 20 * USEC_PER_SEC);
264 watcher_step_complete(WATCHER_STEP_ID_STOP_ALL_REMAINING_WORKER_THREADS);
265
266 cancel_main_threads();
267 watcher_step_complete(WATCHER_STEP_ID_CANCEL_MAIN_THREADS);
268
269 if (abnormal) {
270 watcher_step_complete(WATCHER_STEP_ID_STOP_COLLECTION_FOR_ALL_HOSTS);
271 watcher_step_complete(WATCHER_STEP_ID_WAIT_FOR_DBENGINE_COLLECTORS_TO_FINISH);
272 watcher_step_complete(WATCHER_STEP_ID_STOP_DBENGINE_TIERS);
273 watcher_step_complete(WATCHER_STEP_ID_STOP_METASYNC_THREADS);
274 }
275 else
276 {
277 // exit cleanly
278 rrd_finalize_collection_for_all_hosts();
279 watcher_step_complete(WATCHER_STEP_ID_STOP_COLLECTION_FOR_ALL_HOSTS);
280
281 #ifdef ENABLE_DBENGINE
282 if(dbengine_enabled) {
283 // flush anything remaining and wait for collectors to finish
284 rrdeng_flush_everything_and_wait(true, true, false);
285 watcher_step_complete(WATCHER_STEP_ID_WAIT_FOR_DBENGINE_COLLECTORS_TO_FINISH);
286
287 ND_THREAD **th = callocz(nd_profile.storage_tiers, sizeof(*th));
288 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++)
289 th[tier] = nd_thread_create("rrdeng-exit", NETDATA_THREAD_OPTION_DEFAULT, rrdeng_exit_background, multidb_ctx[tier]);
290
291 // flush anything remaining again - just in case
292 rrdeng_flush_everything_and_wait(true, true, false);
293
294 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++)
295 nd_thread_join(th[tier]);
296
297 freez(th);
298
299 dbengine_shutdown();
300 watcher_step_complete(WATCHER_STEP_ID_STOP_DBENGINE_TIERS);
301 }
302 else {
303 // Skip these steps
304 watcher_step_complete(WATCHER_STEP_ID_WAIT_FOR_DBENGINE_COLLECTORS_TO_FINISH);
305 watcher_step_complete(WATCHER_STEP_ID_STOP_DBENGINE_TIERS);
306 }
307 #else
308 // Skip these steps
309 watcher_step_complete(WATCHER_STEP_ID_WAIT_FOR_DBENGINE_COLLECTORS_TO_FINISH);
310 watcher_step_complete(WATCHER_STEP_ID_STOP_DBENGINE_TIERS);
311 #endif
312
313 metadata_sync_shutdown();
314 ml_fini();
315 watcher_step_complete(WATCHER_STEP_ID_STOP_METASYNC_THREADS);
316 }
317
318 // Don't register a shutdown event if we crashed
319 if (!abnormal)
320 add_agent_event(EVENT_AGENT_SHUTDOWN_TIME, (int64_t)(now_monotonic_usec() - shutdown_start_time));
321
322 nd_thread_join_threads();
323 watcher_step_complete(WATCHER_STEP_ID_JOIN_STATIC_THREADS);
324
325 sqlite_close_databases();
326 sqlite_library_shutdown();
327 watcher_step_complete(WATCHER_STEP_ID_CLOSE_SQL_DATABASES);
328
329 // unlink the pid
330 if(pidfile && *pidfile && unlink(pidfile) != 0)
331 netdata_log_error("EXIT: cannot unlink pidfile '%s'.", pidfile);
332
333 // unlink the pipe
334 // During the commands_exit() signal-handler path, libuv may already
335 // have unlinked the pipe on close. For other exit paths the command
336 // thread keeps running and we must clean it up here. ENOENT just means
337 // libuv beat us to removing it.
338 const char *pipe = daemon_pipename();
339 if(pipe && *pipe && unlink(pipe) != 0 && errno != ENOENT)
340 netdata_log_error("EXIT: cannot unlink netdatacli socket file '%s'.", pipe);
341
342 watcher_step_complete(WATCHER_STEP_ID_REMOVE_PID_FILE);
343
344 netdata_ssl_cleanup();
345 watcher_step_complete(WATCHER_STEP_ID_FREE_OPENSSL_STRUCTURES);
346
347 watcher_shutdown_end();
348 watcher_thread_stop();
349
350 #if defined(FSANITIZE_ADDRESS)
351 fprintf(stderr, "\n");
352
353 fprintf(stderr, "Stopping spawn server...\n");
354 netdata_main_spawn_server_cleanup();
355
356 fprintf(stderr, "Freeing all RRDHOSTs...\n");
357 mcp_functions_registry_cleanup();
358 rrdhost_free_all();
359 dyncfg_shutdown();
360 rrd_functions_inflight_destroy();
361 health_plugin_destroy();
362 cgroup_netdev_link_destroy();
363 bearer_tokens_destroy();
364
365 fprintf(stderr, "Cleaning up destroyed dictionaries...\n");
366 size_t dictionaries_referenced = cleanup_destroyed_dictionaries(true);
367 if(dictionaries_referenced)
368 fprintf(stderr, "WARNING: There are %zu dictionaries with references in them, that cannot be destroyed.\n",
369 dictionaries_referenced);
370
371 // Always report dictionary allocations during ASAN builds
372 dictionary_print_still_allocated_stacktraces();
373
374 #ifdef ENABLE_DBENGINE
375 // destroy the caches in reverse order (extent and open depend on main cache)
376 fprintf(stderr, "Destroying extent cache (PGC)...\n");
377 pgc_destroy(extent_cache, false);
378 fprintf(stderr, "Destroying open cache (PGC)...\n");
379 pgc_destroy(open_cache, false);
380 fprintf(stderr, "Destroying main cache (PGC)...\n");
381 pgc_destroy(main_cache, false);
382
383 fprintf(stderr, "Destroying metrics registry (MRG)...\n");
384 size_t metrics_referenced = mrg_destroy(main_mrg);
385 if(metrics_referenced)
386 fprintf(stderr, "WARNING: MRG had %zu metrics referenced.\n",
387 metrics_referenced);
388
389 for(size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
390 if(multidb_ctx[tier]) {
391 fprintf(stderr, "Finalizing data files for tier %zu...\n", tier);
392 finalize_rrd_files(multidb_ctx[tier]);
393 memset(multidb_ctx[tier], 0, sizeof(*multidb_ctx[tier]));
394 }
395 }
396 #endif
397
398 fprintf(stderr, "Destroying UUIDMap...\n");
399 size_t uuid_referenced = uuidmap_destroy();
400 if(uuid_referenced)
401 fprintf(stderr, "WARNING: UUIDMAP had %zu UUIDs referenced.\n",
402 uuid_referenced);
403
404 fprintf(stderr, "Freeing configuration resources...\n");
405 claim_config_free();
406 exporting_config_free();
407 stream_config_free();
408 inicfg_free(&cloud_config);
409 inicfg_free(&netdata_config);
410
411 fprintf(stderr, "Cleaning up worker utilization...\n");
412 worker_utilization_cleanup();
413
414 alerts_by_x_cleanup();
415 size_t strings_referenced = string_destroy();
416 if(strings_referenced)
417 fprintf(stderr, "WARNING: STRING has %zu strings still allocated.\n",
418 strings_referenced);
419
420 rrdlabels_aral_destroy(true);
421 fprintf(stderr, "RRDLABELS remaining in registry: %d.\n", rrdlabels_registry_count());
422
423 fprintf(stderr, "All done, exiting...\n");
424 #endif
425
426 if(!exit_when_done) {
427 curl_global_cleanup();
428 return;
429 }
430
431 #ifdef ENABLE_SENTRY
432 if(abnormal)
433 shutdown_on_fatal();
434
435 nd_sentry_fini();
436 curl_global_cleanup();
437 exit(abnormal ? 1 : 0);
438 #else
439 if(abnormal)
440 _exit(1);
441 else {
442 curl_global_cleanup();
443 exit(0);
444 }
445 #endif
446 }
447
448 void netdata_exit_gracefully(EXIT_REASON reason, bool exit_when_done) {
449 exit_initiated_add(reason);
450 FUNCTION_RUN_ONCE();
451 netdata_cleanup_and_exit(reason, false, exit_when_done);
452 }
453
454 // the final callback for the fatal() function
455 void netdata_exit_fatal(void) {
456 netdata_cleanup_and_exit(EXIT_REASON_FATAL, true, true);
457 exit(1);
458 }