| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "daemon-service.h" |
| 4 | |
| 5 | typedef struct service_thread { |
| 6 | pid_t tid; |
| 7 | SERVICE_TYPE services; |
| 8 | char name[ND_THREAD_TAG_MAX + 1]; |
| 9 | bool cancelled; |
| 10 | |
| 11 | ND_THREAD *netdata_thread; |
| 12 | |
| 13 | force_quit_t force_quit_callback; |
| 14 | request_quit_t request_quit_callback; |
| 15 | void *data; |
| 16 | } SERVICE_THREAD; |
| 17 | |
| 18 | struct service_globals { |
| 19 | SPINLOCK lock; |
| 20 | Pvoid_t pid_judy; |
| 21 | } service_globals = { |
| 22 | .pid_judy = NULL, |
| 23 | }; |
| 24 | |
| 25 | SERVICE_THREAD *service_register(request_quit_t request_quit_callback, force_quit_t force_quit_callback, void *data) |
| 26 | { |
| 27 | SERVICE_THREAD *sth = NULL; |
| 28 | pid_t tid = gettid_cached(); |
| 29 | |
| 30 | spinlock_lock(&service_globals.lock); |
| 31 | Pvoid_t *PValue = JudyLIns(&service_globals.pid_judy, tid, PJE0); |
| 32 | if(!*PValue) { |
| 33 | sth = callocz(1, sizeof(SERVICE_THREAD)); |
| 34 | sth->tid = tid; |
| 35 | sth->request_quit_callback = request_quit_callback; |
| 36 | sth->force_quit_callback = force_quit_callback; |
| 37 | sth->data = data; |
| 38 | *PValue = sth; |
| 39 | sth->netdata_thread = nd_thread_self(); |
| 40 | |
| 41 | const char *name = nd_thread_tag(); |
| 42 | if(!name) name = ""; |
| 43 | strncpyz(sth->name, name, sizeof(sth->name) - 1); |
| 44 | } |
| 45 | else { |
| 46 | sth = *PValue; |
| 47 | } |
| 48 | spinlock_unlock(&service_globals.lock); |
| 49 | |
| 50 | return sth; |
| 51 | } |
| 52 | |
| 53 | void service_exits(void) { |
| 54 | pid_t tid = gettid_cached(); |
| 55 | |
| 56 | spinlock_lock(&service_globals.lock); |
| 57 | Pvoid_t *PValue = JudyLGet(service_globals.pid_judy, tid, PJE0); |
| 58 | if(PValue) { |
| 59 | freez(*PValue); |
| 60 | JudyLDel(&service_globals.pid_judy, tid, PJE0); |
| 61 | } |
| 62 | spinlock_unlock(&service_globals.lock); |
| 63 | } |
| 64 | |
| 65 | bool service_running(SERVICE_TYPE service) { |
| 66 | static __thread SERVICE_THREAD *sth = NULL; |
| 67 | |
| 68 | if(unlikely(!sth)) |
| 69 | sth = service_register(NULL, NULL, NULL); |
| 70 | |
| 71 | sth->services |= service; |
| 72 | |
| 73 | return !nd_thread_signaled_to_cancel() && !exit_initiated_get(); |
| 74 | } |
| 75 | |
| 76 | void service_signal_exit(SERVICE_TYPE service) { |
| 77 | spinlock_lock(&service_globals.lock); |
| 78 | |
| 79 | Pvoid_t *PValue; |
| 80 | Word_t tid = 0; |
| 81 | bool first = true; |
| 82 | while((PValue = JudyLFirstThenNext(service_globals.pid_judy, &tid, &first))) { |
| 83 | SERVICE_THREAD *sth = *PValue; |
| 84 | |
| 85 | if((sth->services & service)) { |
| 86 | nd_thread_signal_cancel(sth->netdata_thread); |
| 87 | nd_log_daemon(NDLP_DEBUG, "SERVICE: Signal to stop : %s", sth->name); |
| 88 | if(sth->request_quit_callback) { |
| 89 | spinlock_unlock(&service_globals.lock); |
| 90 | sth->request_quit_callback(sth->data); |
| 91 | spinlock_lock(&service_globals.lock); |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | spinlock_unlock(&service_globals.lock); |
| 96 | } |
| 97 | |
| 98 | static void service_to_buffer(BUFFER *wb, SERVICE_TYPE service) { |
| 99 | if(service & SERVICE_COLLECTORS) |
| 100 | buffer_strcat(wb, "COLLECTORS "); |
| 101 | if(service & SERVICE_REPLICATION) |
| 102 | buffer_strcat(wb, "REPLICATION "); |
| 103 | if(service & ABILITY_WEB_REQUESTS) |
| 104 | buffer_strcat(wb, "WEB_REQUESTS "); |
| 105 | if(service & SERVICE_WEB_SERVER) |
| 106 | buffer_strcat(wb, "WEB_SERVER "); |
| 107 | if(service & SERVICE_ACLK) |
| 108 | buffer_strcat(wb, "ACLK "); |
| 109 | if(service & SERVICE_HEALTH) |
| 110 | buffer_strcat(wb, "HEALTH "); |
| 111 | if(service & SERVICE_STREAMING) |
| 112 | buffer_strcat(wb, "STREAMING "); |
| 113 | if(service & ABILITY_STREAMING_CONNECTIONS) |
| 114 | buffer_strcat(wb, "STREAMING_CONNECTIONS "); |
| 115 | if(service & SERVICE_CONTEXT) |
| 116 | buffer_strcat(wb, "CONTEXT "); |
| 117 | if(service & SERVICE_ANALYTICS) |
| 118 | buffer_strcat(wb, "ANALYTICS "); |
| 119 | if(service & SERVICE_EXPORTERS) |
| 120 | buffer_strcat(wb, "EXPORTERS "); |
| 121 | if(service & SERVICE_HTTPD) |
| 122 | buffer_strcat(wb, "HTTPD "); |
| 123 | } |
| 124 | |
| 125 | bool service_wait_exit(SERVICE_TYPE service, usec_t timeout_ut) { |
| 126 | BUFFER *service_list = buffer_create(1024, NULL); |
| 127 | BUFFER *thread_list = buffer_create(1024, NULL); |
| 128 | usec_t started_ut = now_monotonic_usec(), ended_ut; |
| 129 | size_t running; |
| 130 | SERVICE_TYPE running_services = 0; |
| 131 | |
| 132 | // cancel the threads |
| 133 | running = 0; |
| 134 | running_services = 0; |
| 135 | { |
| 136 | buffer_flush(thread_list); |
| 137 | |
| 138 | spinlock_lock(&service_globals.lock); |
| 139 | |
| 140 | Pvoid_t *PValue; |
| 141 | Word_t tid = 0; |
| 142 | bool first = true; |
| 143 | while((PValue = JudyLFirstThenNext(service_globals.pid_judy, &tid, &first))) { |
| 144 | SERVICE_THREAD *sth = *PValue; |
| 145 | if(sth->services & service && sth->tid != gettid_cached() && !sth->cancelled) { |
| 146 | sth->cancelled = true; |
| 147 | nd_thread_signal_cancel(sth->netdata_thread); |
| 148 | if(running) |
| 149 | buffer_strcat(thread_list, ", "); |
| 150 | |
| 151 | buffer_sprintf(thread_list, "'%s' (%d)", sth->name, sth->tid); |
| 152 | |
| 153 | running++; |
| 154 | running_services |= sth->services & service; |
| 155 | |
| 156 | if(sth->force_quit_callback) { |
| 157 | spinlock_unlock(&service_globals.lock); |
| 158 | sth->force_quit_callback(sth->data); |
| 159 | spinlock_lock(&service_globals.lock); |
| 160 | continue; |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | spinlock_unlock(&service_globals.lock); |
| 166 | } |
| 167 | |
| 168 | service_signal_exit(service); |
| 169 | |
| 170 | // signal them to stop |
| 171 | size_t last_running = 0; |
| 172 | usec_t sleep_ut = 50 * USEC_PER_MS; |
| 173 | size_t log_countdown_ut = sleep_ut; |
| 174 | do { |
| 175 | last_running = running; |
| 176 | running = 0; |
| 177 | running_services = 0; |
| 178 | buffer_flush(thread_list); |
| 179 | |
| 180 | spinlock_lock(&service_globals.lock); |
| 181 | |
| 182 | Pvoid_t *PValue; |
| 183 | Word_t tid = 0; |
| 184 | bool first = true; |
| 185 | while((PValue = JudyLFirstThenNext(service_globals.pid_judy, &tid, &first))) { |
| 186 | SERVICE_THREAD *sth = *PValue; |
| 187 | if(sth->services & service && sth->tid != gettid_cached()) { |
| 188 | if(running) |
| 189 | buffer_strcat(thread_list, ", "); |
| 190 | |
| 191 | buffer_sprintf(thread_list, "'%s' (%d)", sth->name, sth->tid); |
| 192 | |
| 193 | running_services |= sth->services & service; |
| 194 | running++; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | spinlock_unlock(&service_globals.lock); |
| 199 | |
| 200 | if(running) { |
| 201 | log_countdown_ut -= (log_countdown_ut >= sleep_ut) ? sleep_ut : log_countdown_ut; |
| 202 | if(log_countdown_ut == 0 || running != last_running) { |
| 203 | log_countdown_ut = 20 * sleep_ut; |
| 204 | |
| 205 | buffer_flush(service_list); |
| 206 | service_to_buffer(service_list, running_services); |
| 207 | netdata_log_info("SERVICE CONTROL: waiting for the following %zu services [ %s] to exit: %s", |
| 208 | running, buffer_tostring(service_list), |
| 209 | running <= 10 ? buffer_tostring(thread_list) : ""); |
| 210 | } |
| 211 | |
| 212 | sleep_usec(sleep_ut); |
| 213 | } |
| 214 | |
| 215 | ended_ut = now_monotonic_usec(); |
| 216 | } while(running && ((ended_ut - started_ut) < timeout_ut)); |
| 217 | |
| 218 | if(running) { |
| 219 | buffer_flush(service_list); |
| 220 | service_to_buffer(service_list, running_services); |
| 221 | netdata_log_info("SERVICE CONTROL: " |
| 222 | "the following %zu service(s) [ %s] take too long to exit: %s; " |
| 223 | "giving up on them...", |
| 224 | running, buffer_tostring(service_list), |
| 225 | buffer_tostring(thread_list)); |
| 226 | } |
| 227 | |
| 228 | buffer_free(thread_list); |
| 229 | buffer_free(service_list); |
| 230 | |
| 231 | return (running == 0); |
| 232 | } |