Improve the impact of health code on netdata scalability (#8407)
* Add support for spawning processes without pipes. * Port health_alarm_execute() from mypopen() to netdata_spawn() * Make alarm notifications asynchronous within a single health thread iteration * Initial version of spawn server. * preliminary integration of spawn client with health
Markos Fountoulakis committed
May 14, 2020 at 11:57 UTC
6393b2f535c993de9f341d2245ad8ba327694281
16 files changed
+1216
-48
CMakeLists.txt
+8
@@ -641,6 +641,13 @@ set(ACLK_PLUGIN_FILES
641
aclk/mqtt.h
642
)
643
644
+set(SPAWN_PLUGIN_FILES
645
+ spawn/spawn.c
646
+ spawn/spawn_server.c
647
+ spawn/spawn_client.c
648
+ spawn/spawn.h
649
+ )
650
+
651
set(ACLK_STATIC_LIBS
652
${CMAKE_SOURCE_DIR}/externaldeps/mosquitto/libmosquitto.a
653
${CMAKE_SOURCE_DIR}/externaldeps/libwebsockets/libwebsockets.a
@@ -741,6 +748,7 @@ set(NETDATA_FILES
748
${STREAMING_PLUGIN_FILES}
749
${WEB_PLUGIN_FILES}
750
${CLAIM_PLUGIN_FILES}
751
+ ${SPAWN_PLUGIN_FILES}
752
)
753
754
set(NETDATACLI_FILES
Makefile.am
+9
@@ -112,6 +112,7 @@ SUBDIRS += \
112
web \
113
claim \
114
aclk \
115
+ spawn \
116
$(NULL)
117
118
@@ -497,6 +498,13 @@ endif
498
499
500
501
+SPAWN_PLUGIN_FILES = \
502
+ spawn/spawn.c \
503
+ spawn/spawn_server.c \
504
+ spawn/spawn_client.c \
505
+ spawn/spawn.h \
506
+ $(NULL)
507
+
508
EXPORTING_ENGINE_FILES = \
509
exporting/exporting_engine.c \
510
exporting/exporting_engine.h \
@@ -595,6 +603,7 @@ NETDATA_FILES = \
603
$(WEB_PLUGIN_FILES) \
604
$(CLAIM_FILES) \
605
$(ACLK_FILES) \
606
+ $(SPAWN_PLUGIN_FILES) \
607
$(NULL)
608
609
if FREEBSD
configure.ac
+1
@@ -1490,6 +1490,7 @@ AC_CONFIG_FILES([
1490
web/server/static/Makefile
1491
claim/Makefile
1492
aclk/Makefile
1493
+ spawn/Makefile
1494
])
1495
AC_OUTPUT
1496
daemon/common.h
+3
@@ -68,6 +68,9 @@
68
// netdata agent cloud link
69
#include "aclk/agent_cloud_link.h"
70
71
+// netdata agent spawn server
72
+#include "spawn/spawn.h"
73
+
74
// the netdata deamon
75
#include "daemon.h"
76
#include "main.h"
daemon/main.c
+8
@@ -906,6 +906,11 @@ int main(int argc, char **argv) {
906
else i++;
907
}
908
}
909
+ if (argc > 1 && strcmp(argv[1], SPAWN_SERVER_COMMAND_LINE_ARGUMENT) == 0) {
910
+ // don't run netdata, this is the spawn server
911
+ spawn_server();
912
+ exit(0);
913
+ }
914
915
// parse options
916
{
@@ -1377,6 +1382,9 @@ int main(int argc, char **argv) {
1382
1383
netdata_threads_init_after_fork((size_t)config_get_number(CONFIG_SECTION_GLOBAL, "pthread stack size", (long)default_stacksize));
1384
1385
+ // fork the spawn server
1386
+ spawn_init();
1387
+
1388
// ------------------------------------------------------------------------
1389
// initialize rrd, registry, health, rrdpush, etc.
1390
database/rrd.h
+3
@@ -576,6 +576,7 @@ struct alarm_entry {
576
char *recipient;
577
time_t exec_run_timestamp;
578
int exec_code;
579
+ uint64_t exec_spawn_serial;
580
581
char *source;
582
char *units;
@@ -601,6 +602,8 @@ struct alarm_entry {
602
time_t last_repeat;
603
604
struct alarm_entry *next;
605
+ struct alarm_entry *next_in_progress;
606
+ struct alarm_entry *prev_in_progress;
607
};
608
609
health/health.c
+72
-14
@@ -11,6 +11,46 @@ struct health_cmdapi_thread_status {
11
unsigned int default_health_enabled = 1;
12
char *silencers_filename;
13
14
+// the queue of executed alarm notifications that haven't been waited for yet
15
+static struct {
16
+ ALARM_ENTRY *head; // oldest
17
+ ALARM_ENTRY *tail; // latest
18
+} alarm_notifications_in_progress = {NULL, NULL};
19
+
20
+static inline void enqueue_alarm_notify_in_progress(ALARM_ENTRY *ae)
21
+{
22
+ ae->prev_in_progress = NULL;
23
+ ae->next_in_progress = NULL;
24
+
25
+ if (NULL != alarm_notifications_in_progress.tail) {
26
+ ae->prev_in_progress = alarm_notifications_in_progress.tail;
27
+ alarm_notifications_in_progress.tail->next_in_progress = ae;
28
+ }
29
+ if (NULL == alarm_notifications_in_progress.head) {
30
+ alarm_notifications_in_progress.head = ae;
31
+ }
32
+ alarm_notifications_in_progress.tail = ae;
33
+
34
+}
35
+
36
+static inline void unlink_alarm_notify_in_progress(ALARM_ENTRY *ae)
37
+{
38
+ struct alarm_entry *prev = ae->prev_in_progress;
39
+ struct alarm_entry *next = ae->next_in_progress;
40
+
41
+ if (NULL != prev) {
42
+ prev->next_in_progress = next;
43
+ }
44
+ if (NULL != next) {
45
+ next->prev_in_progress = prev;
46
+ }
47
+ if (ae == alarm_notifications_in_progress.head) {
48
+ alarm_notifications_in_progress.head = next;
49
+ }
50
+ if (ae == alarm_notifications_in_progress.tail) {
51
+ alarm_notifications_in_progress.tail = prev;
52
+ }
53
+}
54
// ----------------------------------------------------------------------------
55
// health initialization
56
@@ -265,7 +305,6 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
305
}
306
307
static char command_to_run[ALARM_EXEC_COMMAND_LENGTH + 1];
268
- pid_t command_pid;
308
309
const char *exec = (ae->exec) ? ae->exec : host->health_default_exec;
310
const char *recipient = (ae->recipient) ? ae->recipient : host->health_default_recipient;
@@ -321,25 +360,30 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
360
);
361
362
ae->flags |= HEALTH_ENTRY_FLAG_EXEC_RUN;
324
- ae->exec_run_timestamp = now_realtime_sec();
363
+ ae->exec_run_timestamp = now_realtime_sec(); /* will be updated by real time after spawning */
364
365
debug(D_HEALTH, "executing command '%s'", command_to_run);
327
- FILE *fp = mypopen(command_to_run, &command_pid);
328
- if(!fp) {
329
- error("HEALTH: Cannot popen(\"%s\", \"r\").", command_to_run);
330
- goto done;
331
- }
332
- debug(D_HEALTH, "HEALTH reading from command (discarding command's output)");
333
- char buffer[100 + 1];
334
- while(fgets(buffer, 100, fp) != NULL) ;
335
- ae->exec_code = mypclose(fp, command_pid);
366
+ ae->flags |= HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
367
+ ae->exec_spawn_serial = spawn_enq_cmd(command_to_run);
368
+ enqueue_alarm_notify_in_progress(ae);
369
+
370
+ return; //health_alarm_wait_for_execution
371
+done:
372
+ health_alarm_log_save(host, ae);
373
+}
374
+
375
+static inline void health_alarm_wait_for_execution(ALARM_ENTRY *ae) {
376
+ if (!(ae->flags & HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS))
377
+ return;
378
+
379
+ spawn_wait_cmd(ae->exec_spawn_serial, &ae->exec_code, &ae->exec_run_timestamp);
380
debug(D_HEALTH, "done executing command - returned with code %d", ae->exec_code);
381
+ ae->flags &= ~HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
382
383
if(ae->exec_code != 0)
384
ae->flags |= HEALTH_ENTRY_FLAG_EXEC_FAILED;
385
341
-done:
342
- health_alarm_log_save(host, ae);
386
+ unlink_alarm_notify_in_progress(ae);
387
}
388
389
static inline void health_process_notifications(RRDHOST *host, ALARM_ENTRY *ae) {
@@ -401,6 +445,7 @@ static inline void health_alarm_log_process(RRDHOST *host) {
445
ALARM_ENTRY *t = ae->next;
446
447
if(likely(!alarm_entry_isrepeating(host, ae))) {
448
+ health_alarm_wait_for_execution(ae);
449
health_alarm_log_free_one_nochecks_nounlink(ae);
450
host->health_log.count--;
451
}
@@ -945,6 +990,7 @@ void *health_main(void *ptr) {
990
rc->rrdcalc_flags |= RRDCALC_FLAG_RUN_ONCE;
991
health_process_notifications(host, ae);
992
debug(D_HEALTH, "Notification sent for the repeating alarm %u.", ae->alarm_id);
993
+ health_alarm_wait_for_execution(ae);
994
health_alarm_log_free_one_nochecks_nounlink(ae);
995
}
996
}
@@ -959,11 +1005,23 @@ void *health_main(void *ptr) {
1005
// and cleanup
1006
health_alarm_log_process(host);
1007
962
- if (unlikely(netdata_exit))
1008
+ if (unlikely(netdata_exit)) {
1009
+ // wait for all notifications to finish before allowing health to be cleaned up
1010
+ ALARM_ENTRY *ae;
1011
+ while (NULL != (ae = alarm_notifications_in_progress.head)) {
1012
+ health_alarm_wait_for_execution(ae);
1013
+ }
1014
break;
1015
+ }
1016
1017
} /* rrdhost_foreach */
1018
1019
+ // wait for all notifications to finish before allowing health to be cleaned up
1020
+ ALARM_ENTRY *ae;
1021
+ while (NULL != (ae = alarm_notifications_in_progress.head)) {
1022
+ health_alarm_wait_for_execution(ae);
1023
+ }
1024
+
1025
rrd_unlock();
1026
1027
health/health.h
+1
@@ -24,6 +24,7 @@ extern unsigned int default_health_enabled;
24
#define HEALTH_ENTRY_FLAG_EXEC_FAILED 0x00000008
25
#define HEALTH_ENTRY_FLAG_SILENCED 0x00000010
26
#define HEALTH_ENTRY_RUN_ONCE 0x00000020
27
+#define HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS 0x00000040
28
29
#define HEALTH_ENTRY_FLAG_SAVED 0x10000000
30
#define HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION 0x80000000
libnetdata/popen/popen.c
+84
-34
@@ -78,8 +78,16 @@ static void myp_del(pid_t pid) {
78
#define PIPE_READ 0
79
#define PIPE_WRITE 1
80
81
-static inline FILE *custom_popene(const char *command, volatile pid_t *pidptr, char **env) {
82
- FILE *fp;
81
+/* custom_popene flag definitions */
82
+#define FLAG_CREATE_PIPE 1 // Create a pipe like popen() when set, otherwise set stdout to /dev/null
83
+#define FLAG_CLOSE_FD 2 // Close all file descriptors other than STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
84
+
85
+/*
86
+ * Returns -1 on failure, 0 on success. When FLAG_CREATE_PIPE is set, on success set the FILE *fp pointer.
87
+ */
88
+static inline int custom_popene(const char *command, volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp) {
89
+ FILE *fp = NULL;
90
+ int ret = 0; // success by default
91
int pipefd[2], error;
92
pid_t pid;
93
char *const spawn_argv[] = {
@@ -91,23 +99,36 @@ static inline FILE *custom_popene(const char *command, volatile pid_t *pidptr, c
99
posix_spawnattr_t attr;
100
posix_spawn_file_actions_t fa;
101
94
- if (pipe(pipefd) == -1)
95
- return NULL;
96
- if ((fp = fdopen(pipefd[PIPE_READ], "r")) == NULL) {
97
- goto error_after_pipe;
102
+ if (flags & FLAG_CREATE_PIPE) {
103
+ if (pipe(pipefd) == -1)
104
+ return -1;
105
+ if ((fp = fdopen(pipefd[PIPE_READ], "r")) == NULL) {
106
+ goto error_after_pipe;
107
+ }
108
}
109
100
- // Mark all files to be closed by the exec() stage of posix_spawn()
101
- int i;
102
- for (i = (int) (sysconf(_SC_OPEN_MAX) - 1); i >= 0; i--)
103
- if(i != STDIN_FILENO && i != STDERR_FILENO)
104
- (void)fcntl(i, F_SETFD, FD_CLOEXEC);
110
+ if (flags & FLAG_CLOSE_FD) {
111
+ // Mark all files to be closed by the exec() stage of posix_spawn()
112
+ int i;
113
+ for (i = (int) (sysconf(_SC_OPEN_MAX) - 1); i >= 0; i--) {
114
+ if (i != STDIN_FILENO && i != STDERR_FILENO)
115
+ (void) fcntl(i, F_SETFD, FD_CLOEXEC);
116
+ }
117
+ }
118
119
if (!posix_spawn_file_actions_init(&fa)) {
107
- // move the pipe to stdout in the child
108
- if (posix_spawn_file_actions_adddup2(&fa, pipefd[PIPE_WRITE], STDOUT_FILENO)) {
109
- error("posix_spawn_file_actions_adddup2() failed");
110
- goto error_after_posix_spawn_file_actions_init;
120
+ if (flags & FLAG_CREATE_PIPE) {
121
+ // move the pipe to stdout in the child
122
+ if (posix_spawn_file_actions_adddup2(&fa, pipefd[PIPE_WRITE], STDOUT_FILENO)) {
123
+ error("posix_spawn_file_actions_adddup2() failed");
124
+ goto error_after_posix_spawn_file_actions_init;
125
+ }
126
+ } else {
127
+ // set stdout to /dev/null
128
+ if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0)) {
129
+ error("posix_spawn_file_actions_addopen() failed");
130
+ // this is not a fatal error
131
+ }
132
}
133
} else {
134
error("posix_spawn_file_actions_init() failed.");
@@ -136,10 +157,16 @@ static inline FILE *custom_popene(const char *command, volatile pid_t *pidptr, c
157
} else {
158
myp_add_unlock();
159
error("Failed to spawn command: '%s' from parent pid %d.", command, getpid());
139
- fclose(fp);
140
- fp = NULL;
160
+ if (flags & FLAG_CREATE_PIPE) {
161
+ fclose(fp);
162
+ }
163
+ ret = -1;
164
+ }
165
+ if (flags & FLAG_CREATE_PIPE) {
166
+ close(pipefd[PIPE_WRITE]);
167
+ if (0 == ret) // on success set FILE * pointer
168
+ *fpp = fp;
169
}
142
- close(pipefd[PIPE_WRITE]);
170
171
if (!error) {
172
// posix_spawnattr_init() succeeded
@@ -149,19 +176,21 @@ static inline FILE *custom_popene(const char *command, volatile pid_t *pidptr, c
176
if (posix_spawn_file_actions_destroy(&fa))
177
error("posix_spawn_file_actions_destroy");
178
152
- return fp;
179
+ return ret;
180
181
error_after_posix_spawn_file_actions_init:
182
if (posix_spawn_file_actions_destroy(&fa))
183
error("posix_spawn_file_actions_destroy");
184
error_after_pipe:
158
- if (fp)
159
- fclose(fp);
160
- else
161
- close(pipefd[PIPE_READ]);
185
+ if (flags & FLAG_CREATE_PIPE) {
186
+ if (fp)
187
+ fclose(fp);
188
+ else
189
+ close(pipefd[PIPE_READ]);
190
163
- close(pipefd[PIPE_WRITE]);
164
- return NULL;
191
+ close(pipefd[PIPE_WRITE]);
192
+ }
193
+ return -1;
194
}
195
196
// See man environ
@@ -222,26 +251,37 @@ int myp_reap(pid_t pid) {
251
}
252
253
FILE *mypopen(const char *command, volatile pid_t *pidptr) {
225
- return custom_popene(command, pidptr, environ);
254
+ FILE *fp = NULL;
255
+ (void)custom_popene(command, pidptr, environ, FLAG_CREATE_PIPE | FLAG_CLOSE_FD, &fp);
256
+ return fp;
257
}
258
259
FILE *mypopene(const char *command, volatile pid_t *pidptr, char **env) {
229
- return custom_popene(command, pidptr, env);
260
+ FILE *fp = NULL;
261
+ (void)custom_popene(command, pidptr, env, FLAG_CREATE_PIPE | FLAG_CLOSE_FD, &fp);
262
+ return fp;
263
+}
264
+
265
+// returns 0 on success, -1 on failure
266
+int netdata_spawn(const char *command, volatile pid_t *pidptr) {
267
+ return custom_popene(command, pidptr, environ, 0, NULL);
268
}
269
232
-int mypclose(FILE *fp, pid_t pid) {
270
+int custom_pclose(FILE *fp, pid_t pid) {
271
int ret;
272
siginfo_t info;
273
274
debug(D_EXIT, "Request to mypclose() on pid %d", pid);
275
238
- // close the pipe fd
239
- // this is required in musl
240
- // without it the childs do not exit
241
- close(fileno(fp));
276
+ if (fp) {
277
+ // close the pipe fd
278
+ // this is required in musl
279
+ // without it the childs do not exit
280
+ close(fileno(fp));
281
243
- // close the pipe file pointer
244
- fclose(fp);
282
+ // close the pipe file pointer
283
+ fclose(fp);
284
+ }
285
286
errno = 0;
287
@@ -285,3 +325,13 @@ int mypclose(FILE *fp, pid_t pid) {
325
326
return 0;
327
}
328
+
329
+int mypclose(FILE *fp, pid_t pid)
330
+{
331
+ return custom_pclose(fp, pid);
332
+}
333
+
334
+int netdata_spawn_waitpid(pid_t pid)
335
+{
336
+ return custom_pclose(NULL, pid);
337
+}
\ No newline at end of file
libnetdata/popen/popen.h
+2
@@ -11,6 +11,8 @@
11
extern FILE *mypopen(const char *command, volatile pid_t *pidptr);
12
extern FILE *mypopene(const char *command, volatile pid_t *pidptr, char **env);
13
extern int mypclose(FILE *fp, pid_t pid);
14
+extern int netdata_spawn(const char *command, volatile pid_t *pidptr);
15
+extern int netdata_spawn_waitpid(pid_t pid);
16
extern void myp_init(void);
17
extern void myp_free(void);
18
extern int myp_reap(pid_t pid);
spawn/Makefile.am
new
+9
@@ -0,0 +1,9 @@
1
+# SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+AUTOMAKE_OPTIONS = subdir-objects
4
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
+
6
+dist_noinst_DATA = \
7
+ README.md \
8
+ $(NULL)
9
+
spawn/README.md
spawn/spawn.c
new
+289
@@ -0,0 +1,289 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "spawn.h"
4
+#include "../database/engine/rrdenginelib.h"
5
+
6
+static uv_thread_t thread;
7
+int spawn_thread_error;
8
+int spawn_thread_shutdown;
9
+
10
+struct spawn_queue spawn_cmd_queue;
11
+
12
+static struct spawn_cmd_info *create_spawn_cmd(char *command_to_run)
13
+{
14
+ struct spawn_cmd_info *cmdinfo;
15
+
16
+ cmdinfo = mallocz(sizeof(*cmdinfo));
17
+ assert(0 == uv_cond_init(&cmdinfo->cond));
18
+ assert(0 == uv_mutex_init(&cmdinfo->mutex));
19
+ cmdinfo->serial = 0; /* invalid */
20
+ cmdinfo->command_to_run = strdupz(command_to_run);
21
+ cmdinfo->exit_status = -1; /* invalid */
22
+ cmdinfo->pid = -1; /* invalid */
23
+ cmdinfo->flags = 0;
24
+
25
+ return cmdinfo;
26
+}
27
+
28
+void destroy_spawn_cmd(struct spawn_cmd_info *cmdinfo)
29
+{
30
+ uv_cond_destroy(&cmdinfo->cond);
31
+ uv_mutex_destroy(&cmdinfo->mutex);
32
+
33
+ freez(cmdinfo->command_to_run);
34
+ freez(cmdinfo);
35
+}
36
+
37
+int spawn_cmd_compare(void *a, void *b)
38
+{
39
+ struct spawn_cmd_info *cmda = a, *cmdb = b;
40
+
41
+ /* No need for mutex, serial will never change and the entries cannot be deallocated yet */
42
+ if (cmda->serial < cmdb->serial) return -1;
43
+ if (cmda->serial > cmdb->serial) return 1;
44
+
45
+ return 0;
46
+}
47
+
48
+static void init_spawn_cmd_queue(void)
49
+{
50
+ spawn_cmd_queue.cmd_tree.root = NULL;
51
+ spawn_cmd_queue.cmd_tree.compar = spawn_cmd_compare;
52
+ spawn_cmd_queue.size = 0;
53
+ spawn_cmd_queue.latest_serial = 0;
54
+ assert(0 == uv_cond_init(&spawn_cmd_queue.cond));
55
+ assert(0 == uv_mutex_init(&spawn_cmd_queue.mutex));
56
+}
57
+
58
+/*
59
+ * Returns serial number of the enqueued command
60
+ */
61
+uint64_t spawn_enq_cmd(char *command_to_run)
62
+{
63
+ unsigned queue_size;
64
+ uint64_t serial;
65
+ avl *avl_ret;
66
+ struct spawn_cmd_info *cmdinfo;
67
+
68
+ cmdinfo = create_spawn_cmd(command_to_run);
69
+
70
+ /* wait for free space in queue */
71
+ uv_mutex_lock(&spawn_cmd_queue.mutex);
72
+ while ((queue_size = spawn_cmd_queue.size) == SPAWN_MAX_OUTSTANDING) {
73
+ uv_cond_wait(&spawn_cmd_queue.cond, &spawn_cmd_queue.mutex);
74
+ }
75
+ assert(queue_size < SPAWN_MAX_OUTSTANDING);
76
+ spawn_cmd_queue.size = queue_size + 1;
77
+
78
+ serial = ++spawn_cmd_queue.latest_serial; /* 0 is invalid */
79
+ cmdinfo->serial = serial; /* No need to take the cmd mutex since it is unreachable at the moment */
80
+
81
+ /* enqueue command */
82
+ avl_ret = avl_insert(&spawn_cmd_queue.cmd_tree, (avl *)cmdinfo);
83
+ assert(avl_ret == (avl *)cmdinfo);
84
+ uv_mutex_unlock(&spawn_cmd_queue.mutex);
85
+
86
+ /* wake up event loop */
87
+ assert(0 == uv_async_send(&spawn_async));
88
+ return serial;
89
+}
90
+
91
+/*
92
+ * Blocks until command with serial finishes running. Only one thread is allowed to wait per command.
93
+ */
94
+void spawn_wait_cmd(uint64_t serial, int *exit_status, time_t *exec_run_timestamp)
95
+{
96
+ avl *avl_ret;
97
+ struct spawn_cmd_info tmp, *cmdinfo;
98
+
99
+ tmp.serial = serial;
100
+
101
+ uv_mutex_lock(&spawn_cmd_queue.mutex);
102
+ avl_ret = avl_search(&spawn_cmd_queue.cmd_tree, (avl *)&tmp);
103
+ uv_mutex_unlock(&spawn_cmd_queue.mutex);
104
+
105
+ assert(avl_ret); /* Could be NULL if more than 1 threads wait for the command */
106
+ cmdinfo = (struct spawn_cmd_info *)avl_ret;
107
+
108
+ uv_mutex_lock(&cmdinfo->mutex);
109
+ while (!(cmdinfo->flags & SPAWN_CMD_DONE)) {
110
+ /* Only 1 thread is allowed to wait for this command to finish */
111
+ uv_cond_wait(&cmdinfo->cond, &cmdinfo->mutex);
112
+ }
113
+ uv_mutex_unlock(&cmdinfo->mutex);
114
+
115
+ spawn_deq_cmd(cmdinfo);
116
+ *exit_status = cmdinfo->exit_status;
117
+ *exec_run_timestamp = cmdinfo->exec_run_timestamp;
118
+
119
+ destroy_spawn_cmd(cmdinfo);
120
+}
121
+
122
+void spawn_deq_cmd(struct spawn_cmd_info *cmdinfo)
123
+{
124
+ unsigned queue_size;
125
+ avl *avl_ret;
126
+
127
+ uv_mutex_lock(&spawn_cmd_queue.mutex);
128
+ queue_size = spawn_cmd_queue.size;
129
+ assert(queue_size);
130
+ /* dequeue command */
131
+ avl_ret = avl_remove(&spawn_cmd_queue.cmd_tree, (avl *)cmdinfo);
132
+ assert(avl_ret);
133
+
134
+ spawn_cmd_queue.size = queue_size - 1;
135
+
136
+ /* wake up callers */
137
+ uv_cond_signal(&spawn_cmd_queue.cond);
138
+ uv_mutex_unlock(&spawn_cmd_queue.mutex);
139
+}
140
+
141
+/*
142
+ * Must be called from the spawn client event loop context. This way no mutex is needed because the event loop is the
143
+ * only writer as far as struct spawn_cmd_info entries are concerned.
144
+ */
145
+static int find_unprocessed_spawn_cmd_cb(void *entry, void *data)
146
+{
147
+ struct spawn_cmd_info **cmdinfop = data, *cmdinfo = entry;
148
+
149
+ if (!(cmdinfo->flags & SPAWN_CMD_PROCESSED)) {
150
+ *cmdinfop = cmdinfo;
151
+ return -1; /* break tree traversal */
152
+ }
153
+ return 0; /* continue traversing */
154
+}
155
+
156
+struct spawn_cmd_info *spawn_get_unprocessed_cmd(void)
157
+{
158
+ struct spawn_cmd_info *cmdinfo;
159
+ unsigned queue_size;
160
+ int ret;
161
+
162
+ uv_mutex_lock(&spawn_cmd_queue.mutex);
163
+ queue_size = spawn_cmd_queue.size;
164
+ if (queue_size == 0) {
165
+ uv_mutex_unlock(&spawn_cmd_queue.mutex);
166
+ return NULL;
167
+ }
168
+ /* find command */
169
+ cmdinfo = NULL;
170
+ ret = avl_traverse(&spawn_cmd_queue.cmd_tree, find_unprocessed_spawn_cmd_cb, (void *)&cmdinfo);
171
+ if (-1 != ret) { /* no commands available for processing */
172
+ uv_mutex_unlock(&spawn_cmd_queue.mutex);
173
+ return NULL;
174
+ }
175
+ uv_mutex_unlock(&spawn_cmd_queue.mutex);
176
+
177
+ return cmdinfo;
178
+}
179
+
180
+/**
181
+ * This function spawns a process that shares a libuv IPC pipe with the caller and performs spawn server duties.
182
+ * The spawn server process will close all open file descriptors except for the pipe, UV_STDOUT_FD, and UV_STDERR_FD.
183
+ * The caller has to be the netdata user as configured.
184
+ *
185
+ * @param loop the libuv loop of the caller context
186
+ * @param spawn_channel the birectional libuv IPC pipe that the server and the caller will share
187
+ * @param process the spawn server libuv process context
188
+ * @return 0 on success or the libuv error code
189
+ */
190
+int create_spawn_server(uv_loop_t *loop, uv_pipe_t *spawn_channel, uv_process_t *process)
191
+{
192
+ uv_process_options_t options = {0};
193
+ size_t exepath_size;
194
+ char exepath[FILENAME_MAX];
195
+ char *args[3];
196
+ int ret;
197
+#define SPAWN_SERVER_DESCRIPTORS (3)
198
+ uv_stdio_container_t stdio[SPAWN_SERVER_DESCRIPTORS];
199
+
200
+ exepath_size = sizeof(exepath);
201
+ ret = uv_exepath(exepath, &exepath_size);
202
+ assert(ret == 0);
203
+
204
+ exepath[exepath_size] = '\0';
205
+ args[0] = exepath;
206
+ args[1] = SPAWN_SERVER_COMMAND_LINE_ARGUMENT;
207
+ args[2] = NULL;
208
+
209
+ memset(&options, 0, sizeof(options));
210
+ options.file = exepath;
211
+ options.args = args;
212
+ options.exit_cb = NULL; //exit_cb;
213
+ options.stdio = stdio;
214
+ options.stdio_count = SPAWN_SERVER_DESCRIPTORS;
215
+
216
+ stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE;
217
+ stdio[0].data.stream = (uv_stream_t *)spawn_channel; /* bidirectional libuv pipe */
218
+ stdio[1].flags = UV_INHERIT_FD;
219
+ stdio[1].data.fd = 1 /* UV_STDOUT_FD */;
220
+ stdio[2].flags = UV_INHERIT_FD;
221
+ stdio[2].data.fd = 2 /* UV_STDERR_FD */;
222
+
223
+ ret = uv_spawn(loop, process, &options); /* execute the netdata binary again as the netdata user */
224
+ assert(ret == 0);
225
+
226
+ return ret;
227
+}
228
+
229
+#define CONCURRENT_SPAWNS 16
230
+#define SPAWN_ITERATIONS 10000
231
+#undef CONCURRENT_STRESS_TEST
232
+
233
+void spawn_init(void)
234
+{
235
+ struct completion completion;
236
+ int error;
237
+
238
+ info("Initializing spawn client.");
239
+
240
+ init_spawn_cmd_queue();
241
+
242
+ init_completion(&completion);
243
+ error = uv_thread_create(&thread, spawn_client, &completion);
244
+ if (error) {
245
+ error("uv_thread_create(): %s", uv_strerror(error));
246
+ goto after_error;
247
+ }
248
+ /* wait for spawn client thread to initialize */
249
+ wait_for_completion(&completion);
250
+ destroy_completion(&completion);
251
+ uv_thread_set_name_np(thread, "DAEMON_SPAWN");
252
+
253
+ if (spawn_thread_error) {
254
+ error = uv_thread_join(&thread);
255
+ if (error) {
256
+ error("uv_thread_create(): %s", uv_strerror(error));
257
+ }
258
+ goto after_error;
259
+ }
260
+#ifdef CONCURRENT_STRESS_TEST
261
+ signals_reset();
262
+ signals_unblock();
263
+
264
+ sleep(60);
265
+ uint64_t serial[CONCURRENT_SPAWNS];
266
+ for (int j = 0 ; j < SPAWN_ITERATIONS ; ++j) {
267
+ for (int i = 0; i < CONCURRENT_SPAWNS; ++i) {
268
+ char cmd[64];
269
+ sprintf(cmd, "echo CONCURRENT_STRESS_TEST %d 1>&2", j * CONCURRENT_SPAWNS + i + 1);
270
+ serial[i] = spawn_enq_cmd(cmd);
271
+ info("Queued command %s for spawning.", cmd);
272
+ }
273
+ int exit_status;
274
+ time_t exec_run_timestamp;
275
+ for (int i = 0; i < CONCURRENT_SPAWNS; ++i) {
276
+ info("Started waiting for serial %llu exit status %d run timestamp %llu.", serial[i], exit_status,
277
+ exec_run_timestamp);
278
+ spawn_wait_cmd(serial[i], &exit_status, &exec_run_timestamp);
279
+ info("Finished waiting for serial %llu exit status %d run timestamp %llu.", serial[i], exit_status,
280
+ exec_run_timestamp);
281
+ }
282
+ }
283
+ exit(0);
284
+#endif
285
+ return;
286
+
287
+ after_error:
288
+ error("Failed to initialize spawn service. The alarms notifications will not be spawned.");
289
+}
spawn/spawn.h
new
+109
@@ -0,0 +1,109 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#ifndef NETDATA_SPAWN_H
4
+#define NETDATA_SPAWN_H 1
5
+
6
+#include "../daemon/common.h"
7
+
8
+#define SPAWN_SERVER_COMMAND_LINE_ARGUMENT "--special-spawn-server"
9
+
10
+typedef enum spawn_protocol {
11
+ SPAWN_PROT_EXEC_CMD = 0,
12
+ SPAWN_PROT_SPAWN_RESULT,
13
+ SPAWN_PROT_CMD_EXIT_STATUS
14
+} spawn_prot_t;
15
+
16
+struct spawn_prot_exec_cmd {
17
+ uint16_t command_length;
18
+ char command_to_run[];
19
+};
20
+
21
+struct spawn_prot_spawn_result {
22
+ pid_t exec_pid; /* 0 if failed to spawn */
23
+ time_t exec_run_timestamp; /* time of successfully spawning the command */
24
+};
25
+
26
+struct spawn_prot_cmd_exit_status {
27
+ int exec_exit_status;
28
+};
29
+
30
+struct spawn_prot_header {
31
+ spawn_prot_t opcode;
32
+ void *handle;
33
+};
34
+
35
+#undef SPAWN_DEBUG /* define to enable debug prints */
36
+
37
+#define SPAWN_MAX_OUTSTANDING (32768)
38
+
39
+#define SPAWN_CMD_PROCESSED 0x00000001
40
+#define SPAWN_CMD_IN_PROGRESS 0x00000002
41
+#define SPAWN_CMD_FAILED_TO_SPAWN 0x00000004
42
+#define SPAWN_CMD_DONE 0x00000008
43
+
44
+struct spawn_cmd_info {
45
+ avl avl;
46
+
47
+ /* concurrency control per command */
48
+ uv_mutex_t mutex;
49
+ uv_cond_t cond; /* users block here until command has finished */
50
+
51
+ uint64_t serial;
52
+ char *command_to_run;
53
+ int exit_status;
54
+ pid_t pid;
55
+ unsigned long flags;
56
+ time_t exec_run_timestamp; /* time of successfully spawning the command */
57
+};
58
+
59
+/* spawn command queue */
60
+struct spawn_queue {
61
+ avl_tree cmd_tree;
62
+
63
+ /* concurrency control of command queue */
64
+ uv_mutex_t mutex;
65
+ uv_cond_t cond;
66
+
67
+ volatile unsigned size;
68
+ uint64_t latest_serial;
69
+};
70
+
71
+struct write_context {
72
+ uv_write_t write_req;
73
+ struct spawn_prot_header header;
74
+ struct spawn_prot_cmd_exit_status exit_status;
75
+ struct spawn_prot_spawn_result spawn_result;
76
+ struct spawn_prot_exec_cmd payload;
77
+};
78
+
79
+extern int spawn_thread_error;
80
+extern int spawn_thread_shutdown;
81
+extern uv_async_t spawn_async;
82
+
83
+void spawn_init(void);
84
+void spawn_server(void);
85
+void spawn_client(void *arg);
86
+void destroy_spawn_cmd(struct spawn_cmd_info *cmdinfo);
87
+uint64_t spawn_enq_cmd(char *command_to_run);
88
+void spawn_wait_cmd(uint64_t serial, int *exit_status, time_t *exec_run_timestamp);
89
+void spawn_deq_cmd(struct spawn_cmd_info *cmdinfo);
90
+struct spawn_cmd_info *spawn_get_unprocessed_cmd(void);
91
+int create_spawn_server(uv_loop_t *loop, uv_pipe_t *spawn_channel, uv_process_t *process);
92
+
93
+/*
94
+ * Copies from the source buffer to the protocol buffer. It advances the source buffer by the amount copied. It
95
+ * subtracts the amount copied from the source length.
96
+ */
97
+static inline void copy_to_prot_buffer(char *prot_buffer, unsigned *prot_buffer_len, unsigned max_to_copy,
98
+ char **source, unsigned *source_len)
99
+{
100
+ unsigned to_copy;
101
+
102
+ to_copy = MIN(max_to_copy, *source_len);
103
+ memcpy(prot_buffer + *prot_buffer_len, *source, to_copy);
104
+ *prot_buffer_len += to_copy;
105
+ *source += to_copy;
106
+ *source_len -= to_copy;
107
+}
108
+
109
+#endif //NETDATA_SPAWN_H
spawn/spawn_client.c
new
+241
@@ -0,0 +1,241 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "spawn.h"
4
+#include "../database/engine/rrdenginelib.h"
5
+
6
+static uv_process_t process;
7
+static uv_pipe_t spawn_channel;
8
+static uv_loop_t *loop;
9
+uv_async_t spawn_async;
10
+
11
+static char prot_buffer[MAX_COMMAND_LENGTH];
12
+static unsigned prot_buffer_len = 0;
13
+
14
+static void async_cb(uv_async_t *handle)
15
+{
16
+ uv_stop(handle->loop);
17
+}
18
+
19
+static void after_pipe_write(uv_write_t* req, int status)
20
+{
21
+ (void)status;
22
+#ifdef SPAWN_DEBUG
23
+ info("CLIENT %s called status=%d", __func__, status);
24
+#endif
25
+ freez(req->data);
26
+}
27
+
28
+static void client_parse_spawn_protocol(unsigned source_len, char *source)
29
+{
30
+ unsigned required_len;
31
+ struct spawn_prot_header *header;
32
+ struct spawn_prot_spawn_result *spawn_result;
33
+ struct spawn_prot_cmd_exit_status *exit_status;
34
+ struct spawn_cmd_info *cmdinfo;
35
+
36
+ while (source_len) {
37
+ required_len = sizeof(*header);
38
+ if (prot_buffer_len < required_len)
39
+ copy_to_prot_buffer(prot_buffer, &prot_buffer_len, required_len - prot_buffer_len, &source, &source_len);
40
+ if (prot_buffer_len < required_len)
41
+ return; /* Source buffer ran out */
42
+
43
+ header = (struct spawn_prot_header *)prot_buffer;
44
+ cmdinfo = (struct spawn_cmd_info *)header->handle;
45
+ assert(NULL != cmdinfo);
46
+
47
+ switch(header->opcode) {
48
+ case SPAWN_PROT_SPAWN_RESULT:
49
+ required_len += sizeof(*spawn_result);
50
+ if (prot_buffer_len < required_len)
51
+ copy_to_prot_buffer(prot_buffer, &prot_buffer_len, required_len - prot_buffer_len, &source, &source_len);
52
+ if (prot_buffer_len < required_len)
53
+ return; /* Source buffer ran out */
54
+
55
+ spawn_result = (struct spawn_prot_spawn_result *)(header + 1);
56
+ uv_mutex_lock(&cmdinfo->mutex);
57
+ cmdinfo->pid = spawn_result->exec_pid;
58
+ if (0 == cmdinfo->pid) { /* Failed to spawn */
59
+#ifdef SPAWN_DEBUG
60
+ info("CLIENT %s SPAWN_PROT_SPAWN_RESULT failed to spawn.", __func__);
61
+#endif
62
+ cmdinfo->flags |= SPAWN_CMD_FAILED_TO_SPAWN | SPAWN_CMD_DONE;
63
+ uv_cond_signal(&cmdinfo->cond);
64
+ } else {
65
+ cmdinfo->exec_run_timestamp = spawn_result->exec_run_timestamp;
66
+ cmdinfo->flags |= SPAWN_CMD_IN_PROGRESS;
67
+#ifdef SPAWN_DEBUG
68
+ info("CLIENT %s SPAWN_PROT_SPAWN_RESULT in progress.", __func__);
69
+#endif
70
+ }
71
+ uv_mutex_unlock(&cmdinfo->mutex);
72
+ prot_buffer_len = 0;
73
+ break;
74
+ case SPAWN_PROT_CMD_EXIT_STATUS:
75
+ required_len += sizeof(*exit_status);
76
+ if (prot_buffer_len < required_len)
77
+ copy_to_prot_buffer(prot_buffer, &prot_buffer_len, required_len - prot_buffer_len, &source, &source_len);
78
+ if (prot_buffer_len < required_len)
79
+ return; /* Source buffer ran out */
80
+
81
+ exit_status = (struct spawn_prot_cmd_exit_status *)(header + 1);
82
+ uv_mutex_lock(&cmdinfo->mutex);
83
+ cmdinfo->exit_status = exit_status->exec_exit_status;
84
+#ifdef SPAWN_DEBUG
85
+ info("CLIENT %s SPAWN_PROT_CMD_EXIT_STATUS %d.", __func__, exit_status->exec_exit_status);
86
+#endif
87
+ cmdinfo->flags |= SPAWN_CMD_DONE;
88
+ uv_cond_signal(&cmdinfo->cond);
89
+ uv_mutex_unlock(&cmdinfo->mutex);
90
+ prot_buffer_len = 0;
91
+ break;
92
+ default:
93
+ assert(0);
94
+ break;
95
+ }
96
+
97
+ }
98
+}
99
+
100
+static void on_pipe_read(uv_stream_t* pipe, ssize_t nread, const uv_buf_t* buf)
101
+{
102
+ if (0 == nread) {
103
+ info("%s: Zero bytes read from spawn pipe.", __func__);
104
+ } else if (UV_EOF == nread) {
105
+ info("EOF found in spawn pipe.");
106
+ } else if (nread < 0) {
107
+ error("%s: %s", __func__, uv_strerror(nread));
108
+ }
109
+
110
+ if (nread < 0) { /* stop stream due to EOF or error */
111
+ (void)uv_read_stop((uv_stream_t *)pipe);
112
+ } else if (nread) {
113
+#ifdef SPAWN_DEBUG
114
+ info("CLIENT %s read %u", __func__, (unsigned)nread);
115
+#endif
116
+ client_parse_spawn_protocol(nread, buf->base);
117
+ }
118
+ if (buf && buf->len) {
119
+ freez(buf->base);
120
+ }
121
+
122
+ if (nread < 0) {
123
+ uv_close((uv_handle_t *)pipe, NULL);
124
+ }
125
+}
126
+
127
+static void on_read_alloc(uv_handle_t* handle,
128
+ size_t suggested_size,
129
+ uv_buf_t* buf)
130
+{
131
+ (void)handle;
132
+ buf->base = mallocz(suggested_size);
133
+ buf->len = suggested_size;
134
+}
135
+
136
+static void spawn_process_cmd(struct spawn_cmd_info *cmdinfo)
137
+{
138
+ int ret;
139
+ uv_buf_t writebuf[3];
140
+ struct write_context *write_ctx;
141
+
142
+ write_ctx = mallocz(sizeof(*write_ctx));
143
+ write_ctx->write_req.data = write_ctx;
144
+
145
+ uv_mutex_lock(&cmdinfo->mutex);
146
+ cmdinfo->flags |= SPAWN_CMD_PROCESSED;
147
+ uv_mutex_unlock(&cmdinfo->mutex);
148
+
149
+ write_ctx->header.opcode = SPAWN_PROT_EXEC_CMD;
150
+ write_ctx->header.handle = cmdinfo;
151
+ write_ctx->payload.command_length = strlen(cmdinfo->command_to_run);
152
+
153
+ writebuf[0] = uv_buf_init((char *)&write_ctx->header, sizeof(write_ctx->header));
154
+ writebuf[1] = uv_buf_init((char *)&write_ctx->payload, sizeof(write_ctx->payload));
155
+ writebuf[2] = uv_buf_init((char *)cmdinfo->command_to_run, write_ctx->payload.command_length);
156
+
157
+#ifdef SPAWN_DEBUG
158
+ info("CLIENT %s SPAWN_PROT_EXEC_CMD %u", __func__, (unsigned)cmdinfo->serial);
159
+#endif
160
+ ret = uv_write(&write_ctx->write_req, (uv_stream_t *)&spawn_channel, writebuf, 3, after_pipe_write);
161
+ assert(ret == 0);
162
+}
163
+
164
+void spawn_client(void *arg)
165
+{
166
+ int ret;
167
+ struct completion *completion = (struct completion *)arg;
168
+
169
+ loop = mallocz(sizeof(uv_loop_t));
170
+ ret = uv_loop_init(loop);
171
+ if (ret) {
172
+ error("uv_loop_init(): %s", uv_strerror(ret));
173
+ spawn_thread_error = ret;
174
+ goto error_after_loop_init;
175
+ }
176
+ loop->data = NULL;
177
+
178
+ spawn_async.data = NULL;
179
+ ret = uv_async_init(loop, &spawn_async, async_cb);
180
+ if (ret) {
181
+ error("uv_async_init(): %s", uv_strerror(ret));
182
+ spawn_thread_error = ret;
183
+ goto error_after_async_init;
184
+ }
185
+
186
+ ret = uv_pipe_init(loop, &spawn_channel, 1);
187
+ if (ret) {
188
+ error("uv_pipe_init(): %s", uv_strerror(ret));
189
+ spawn_thread_error = ret;
190
+ goto error_after_pipe_init;
191
+ }
192
+ assert(spawn_channel.ipc);
193
+
194
+ ret = create_spawn_server(loop, &spawn_channel, &process);
195
+ if (ret) {
196
+ error("Failed to fork spawn server process.");
197
+ spawn_thread_error = ret;
198
+ goto error_after_spawn_server;
199
+ }
200
+
201
+ spawn_thread_error = 0;
202
+ spawn_thread_shutdown = 0;
203
+ /* wake up initialization thread */
204
+ complete(completion);
205
+
206
+ prot_buffer_len = 0;
207
+ ret = uv_read_start((uv_stream_t *)&spawn_channel, on_read_alloc, on_pipe_read);
208
+ assert(ret == 0);
209
+
210
+ while (spawn_thread_shutdown == 0) {
211
+ struct spawn_cmd_info *cmdinfo;
212
+
213
+ uv_run(loop, UV_RUN_DEFAULT);
214
+ while (NULL != (cmdinfo = spawn_get_unprocessed_cmd())) {
215
+ spawn_process_cmd(cmdinfo);
216
+ }
217
+ }
218
+ /* cleanup operations of the event loop */
219
+ info("Shutting down spawn client event loop.");
220
+ uv_close((uv_handle_t *)&spawn_channel, NULL);
221
+ uv_close((uv_handle_t *)&spawn_async, NULL);
222
+ uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
223
+
224
+ info("Shutting down spawn client loop complete.");
225
+ assert(0 == uv_loop_close(loop));
226
+
227
+ return;
228
+
229
+error_after_spawn_server:
230
+ uv_close((uv_handle_t *)&spawn_channel, NULL);
231
+error_after_pipe_init:
232
+ uv_close((uv_handle_t *)&spawn_async, NULL);
233
+error_after_async_init:
234
+ uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
235
+ assert(0 == uv_loop_close(loop));
236
+error_after_loop_init:
237
+ freez(loop);
238
+
239
+ /* wake up initialization thread */
240
+ complete(completion);
241
+}
spawn/spawn_server.c
new
+377
@@ -0,0 +1,377 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "spawn.h"
4
+
5
+static uv_loop_t *loop;
6
+static uv_pipe_t server_pipe;
7
+
8
+static int server_shutdown = 0;
9
+
10
+static uv_thread_t thread;
11
+
12
+/* spawn outstanding execution structure */
13
+static avl_tree_lock spawn_outstanding_exec_tree;
14
+
15
+static char prot_buffer[MAX_COMMAND_LENGTH];
16
+static unsigned prot_buffer_len = 0;
17
+
18
+struct spawn_execution_info {
19
+ avl avl;
20
+
21
+ void *handle;
22
+ int exit_status;
23
+ pid_t pid;
24
+ struct spawn_execution_info *next;
25
+};
26
+
27
+int spawn_exec_compare(void *a, void *b)
28
+{
29
+ struct spawn_execution_info *spwna = a, *spwnb = b;
30
+
31
+ if (spwna->pid < spwnb->pid) return -1;
32
+ if (spwna->pid > spwnb->pid) return 1;
33
+
34
+ return 0;
35
+}
36
+
37
+/* wake up waiter thread to reap the spawned processes */
38
+static uv_mutex_t wait_children_mutex;
39
+static uv_cond_t wait_children_cond;
40
+static uint8_t spawned_processes;
41
+static struct spawn_execution_info *child_waited_list;
42
+static uv_async_t child_waited_async;
43
+
44
+static inline struct spawn_execution_info *dequeue_child_waited_list(void)
45
+{
46
+ struct spawn_execution_info *exec_info;
47
+
48
+ uv_mutex_lock(&wait_children_mutex);
49
+ if (NULL == child_waited_list) {
50
+ exec_info = NULL;
51
+ } else {
52
+ exec_info = child_waited_list;
53
+ child_waited_list = exec_info->next;
54
+ }
55
+ uv_mutex_unlock(&wait_children_mutex);
56
+
57
+ return exec_info;
58
+}
59
+
60
+static inline void enqueue_child_waited_list(struct spawn_execution_info *exec_info)
61
+{
62
+ uv_mutex_lock(&wait_children_mutex);
63
+ exec_info->next = child_waited_list;
64
+ child_waited_list = exec_info;
65
+ uv_mutex_unlock(&wait_children_mutex);
66
+}
67
+
68
+static void after_pipe_write(uv_write_t *req, int status)
69
+{
70
+ (void)status;
71
+#ifdef SPAWN_DEBUG
72
+ fprintf(stderr, "SERVER %s called status=%d\n", __func__, status);
73
+#endif
74
+ freez(req->data);
75
+}
76
+
77
+static void child_waited_async_cb(uv_async_t *async_handle)
78
+{
79
+ uv_buf_t writebuf[2];
80
+ int ret;
81
+ struct spawn_execution_info *exec_info;
82
+ struct write_context *write_ctx;
83
+
84
+ (void)async_handle;
85
+ while (NULL != (exec_info = dequeue_child_waited_list())) {
86
+ write_ctx = mallocz(sizeof(*write_ctx));
87
+ write_ctx->write_req.data = write_ctx;
88
+
89
+
90
+ write_ctx->header.opcode = SPAWN_PROT_CMD_EXIT_STATUS;
91
+ write_ctx->header.handle = exec_info->handle;
92
+ write_ctx->exit_status.exec_exit_status = exec_info->exit_status;
93
+ writebuf[0] = uv_buf_init((char *) &write_ctx->header, sizeof(write_ctx->header));
94
+ writebuf[1] = uv_buf_init((char *) &write_ctx->exit_status, sizeof(write_ctx->exit_status));
95
+#ifdef SPAWN_DEBUG
96
+ fprintf(stderr, "SERVER %s SPAWN_PROT_CMD_EXIT_STATUS\n", __func__);
97
+#endif
98
+ ret = uv_write(&write_ctx->write_req, (uv_stream_t *) &server_pipe, writebuf, 2, after_pipe_write);
99
+ assert(ret == 0);
100
+
101
+ freez(exec_info);
102
+ }
103
+}
104
+
105
+static void wait_children(void *arg)
106
+{
107
+ siginfo_t i;
108
+ struct spawn_execution_info tmp, *exec_info;
109
+ avl *ret_avl;
110
+
111
+ (void)arg;
112
+ while (!server_shutdown) {
113
+ uv_mutex_lock(&wait_children_mutex);
114
+ while (!spawned_processes) {
115
+ uv_cond_wait(&wait_children_cond, &wait_children_mutex);
116
+ }
117
+ spawned_processes = 0;
118
+ uv_mutex_unlock(&wait_children_mutex);
119
+
120
+ while (!server_shutdown) {
121
+ i.si_pid = 0;
122
+ if (waitid(P_ALL, (id_t) 0, &i, WEXITED) == -1) {
123
+ if (errno != ECHILD)
124
+ fprintf(stderr, "SPAWN: Failed to wait: %s\n", strerror(errno));
125
+ break;
126
+ }
127
+ if (i.si_pid == 0) {
128
+ fprintf(stderr, "SPAWN: No child exited.\n");
129
+ break;
130
+ }
131
+#ifdef SPAWN_DEBUG
132
+ fprintf(stderr, "SPAWN: Successfully waited for pid:%d.\n", (int) i.si_pid);
133
+#endif
134
+ assert(CLD_EXITED == i.si_code);
135
+ tmp.pid = (pid_t)i.si_pid;
136
+ while (NULL == (ret_avl = avl_remove_lock(&spawn_outstanding_exec_tree, (avl *)&tmp))) {
137
+ fprintf(stderr,
138
+ "SPAWN: race condition detected, waiting for child process %d to be indexed.\n",
139
+ (int)tmp.pid);
140
+ (void)sleep_usec(10000); /* 10 msec */
141
+ }
142
+ exec_info = (struct spawn_execution_info *)ret_avl;
143
+ exec_info->exit_status = i.si_status;
144
+ enqueue_child_waited_list(exec_info);
145
+
146
+ /* wake up event loop */
147
+ assert(0 == uv_async_send(&child_waited_async));
148
+ }
149
+ }
150
+}
151
+
152
+void spawn_protocol_execute_command(void *handle, char *command_to_run, uint16_t command_length)
153
+{
154
+ uv_buf_t writebuf[2];
155
+ int ret;
156
+ avl *avl_ret;
157
+ struct spawn_execution_info *exec_info;
158
+ struct write_context *write_ctx;
159
+
160
+ write_ctx = mallocz(sizeof(*write_ctx));
161
+ write_ctx->write_req.data = write_ctx;
162
+
163
+ command_to_run[command_length] = '\0';
164
+#ifdef SPAWN_DEBUG
165
+ fprintf(stderr, "SPAWN: executing command '%s'\n", command_to_run);
166
+#endif
167
+ if (netdata_spawn(command_to_run, &write_ctx->spawn_result.exec_pid)) {
168
+ fprintf(stderr, "SPAWN: Cannot spawn(\"%s\", \"r\").\n", command_to_run);
169
+ write_ctx->spawn_result.exec_pid = 0;
170
+ } else { /* successfully spawned command */
171
+ write_ctx->spawn_result.exec_run_timestamp = now_realtime_sec();
172
+
173
+ /* record it for when the process finishes execution */
174
+ exec_info = mallocz(sizeof(*exec_info));
175
+ exec_info->handle = handle;
176
+ exec_info->pid = write_ctx->spawn_result.exec_pid;
177
+ avl_ret = avl_insert_lock(&spawn_outstanding_exec_tree, (avl *)exec_info);
178
+ assert(avl_ret == (avl *)exec_info);
179
+
180
+ /* wake up the thread that blocks waiting for processes to exit */
181
+ uv_mutex_lock(&wait_children_mutex);
182
+ spawned_processes = 1;
183
+ uv_cond_signal(&wait_children_cond);
184
+ uv_mutex_unlock(&wait_children_mutex);
185
+ }
186
+
187
+ write_ctx->header.opcode = SPAWN_PROT_SPAWN_RESULT;
188
+ write_ctx->header.handle = handle;
189
+ writebuf[0] = uv_buf_init((char *)&write_ctx->header, sizeof(write_ctx->header));
190
+ writebuf[1] = uv_buf_init((char *)&write_ctx->spawn_result, sizeof(write_ctx->spawn_result));
191
+#ifdef SPAWN_DEBUG
192
+ fprintf(stderr, "SERVER %s SPAWN_PROT_SPAWN_RESULT\n", __func__);
193
+#endif
194
+ ret = uv_write(&write_ctx->write_req, (uv_stream_t *)&server_pipe, writebuf, 2, after_pipe_write);
195
+ assert(ret == 0);
196
+}
197
+
198
+static void server_parse_spawn_protocol(unsigned source_len, char *source)
199
+{
200
+ unsigned required_len;
201
+ struct spawn_prot_header *header;
202
+ struct spawn_prot_exec_cmd *payload;
203
+ uint16_t command_length;
204
+
205
+ while (source_len) {
206
+ required_len = sizeof(*header);
207
+ if (prot_buffer_len < required_len)
208
+ copy_to_prot_buffer(prot_buffer, &prot_buffer_len, required_len - prot_buffer_len, &source, &source_len);
209
+ if (prot_buffer_len < required_len)
210
+ return; /* Source buffer ran out */
211
+
212
+ header = (struct spawn_prot_header *)prot_buffer;
213
+ assert(SPAWN_PROT_EXEC_CMD == header->opcode);
214
+ assert(NULL != header->handle);
215
+
216
+ required_len += sizeof(*payload);
217
+ if (prot_buffer_len < required_len)
218
+ copy_to_prot_buffer(prot_buffer, &prot_buffer_len, required_len - prot_buffer_len, &source, &source_len);
219
+ if (prot_buffer_len < required_len)
220
+ return; /* Source buffer ran out */
221
+
222
+ payload = (struct spawn_prot_exec_cmd *)(header + 1);
223
+ command_length = payload->command_length;
224
+
225
+ required_len += command_length;
226
+ if (unlikely(required_len > MAX_COMMAND_LENGTH - 1)) {
227
+ fprintf(stderr, "SPAWN: Ran out of protocol buffer space.\n");
228
+ command_length = (MAX_COMMAND_LENGTH - 1) - (sizeof(*header) + sizeof(*payload));
229
+ required_len = MAX_COMMAND_LENGTH - 1;
230
+ }
231
+ if (prot_buffer_len < required_len)
232
+ copy_to_prot_buffer(prot_buffer, &prot_buffer_len, required_len - prot_buffer_len, &source, &source_len);
233
+ if (prot_buffer_len < required_len)
234
+ return; /* Source buffer ran out */
235
+
236
+ spawn_protocol_execute_command(header->handle, payload->command_to_run, command_length);
237
+ prot_buffer_len = 0;
238
+ }
239
+}
240
+
241
+static void on_pipe_read(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf)
242
+{
243
+ if (0 == nread) {
244
+ fprintf(stderr, "SERVER %s: Zero bytes read from spawn pipe.\n", __func__);
245
+ } else if (UV_EOF == nread) {
246
+ fprintf(stderr, "EOF found in spawn pipe.\n");
247
+ } else if (nread < 0) {
248
+ fprintf(stderr, "%s: %s\n", __func__, uv_strerror(nread));
249
+ }
250
+
251
+ if (nread < 0) { /* stop spawn server due to EOF or error */
252
+ int error;
253
+
254
+ uv_mutex_lock(&wait_children_mutex);
255
+ server_shutdown = 1;
256
+ spawned_processes = 1;
257
+ uv_cond_signal(&wait_children_cond);
258
+ uv_mutex_unlock(&wait_children_mutex);
259
+
260
+ fprintf(stderr, "Shutting down spawn server event loop.\n");
261
+ /* cleanup operations of the event loop */
262
+ (void)uv_read_stop((uv_stream_t *) pipe);
263
+ uv_close((uv_handle_t *)&server_pipe, NULL);
264
+
265
+ error = uv_thread_join(&thread);
266
+ if (error) {
267
+ fprintf(stderr, "uv_thread_create(): %s", uv_strerror(error));
268
+ }
269
+ /* After joining it is safe to destroy child_waited_async */
270
+ uv_close((uv_handle_t *)&child_waited_async, NULL);
271
+ } else if (nread) {
272
+#ifdef SPAWN_DEBUG
273
+ fprintf(stderr, "SERVER %s nread %u\n", __func__, (unsigned)nread);
274
+#endif
275
+ server_parse_spawn_protocol(nread, buf->base);
276
+ }
277
+ if (buf && buf->len) {
278
+ freez(buf->base);
279
+ }
280
+}
281
+
282
+static void on_read_alloc(uv_handle_t *handle,
283
+ size_t suggested_size,
284
+ uv_buf_t* buf)
285
+{
286
+ (void)handle;
287
+ buf->base = mallocz(suggested_size);
288
+ buf->len = suggested_size;
289
+}
290
+
291
+static void ignore_signal_handler(int signo) {
292
+ /*
293
+ * By having a signal handler we allow spawned processes to reset default signal dispositions. Setting SIG_IGN
294
+ * would be inherited by the spawned children which is not desirable.
295
+ */
296
+ (void)signo;
297
+}
298
+
299
+void spawn_server(void)
300
+{
301
+ int error;
302
+
303
+ test_clock_boottime();
304
+ test_clock_monotonic_coarse();
305
+
306
+ // close all open file descriptors, except the standard ones
307
+ // the caller may have left open files (lxc-attach has this issue)
308
+ int fd;
309
+ for(fd = (int)(sysconf(_SC_OPEN_MAX) - 1) ; fd > 2 ; --fd)
310
+ if(fd_is_valid(fd))
311
+ close(fd);
312
+
313
+ // Have the libuv IPC pipe be closed when forking child processes
314
+ (void) fcntl(0, F_SETFD, FD_CLOEXEC);
315
+ fprintf(stderr, "Spawn server is up.\n");
316
+
317
+ // Define signals we want to ignore
318
+ struct sigaction sa;
319
+ int signals_to_ignore[] = {SIGPIPE, SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGUSR1, SIGUSR2, SIGBUS, SIGCHLD};
320
+ unsigned ignore_length = sizeof(signals_to_ignore) / sizeof(signals_to_ignore[0]);
321
+
322
+ unsigned i;
323
+ for (i = 0; i < ignore_length ; ++i) {
324
+ sa.sa_flags = 0;
325
+ sigemptyset(&sa.sa_mask);
326
+ sa.sa_handler = ignore_signal_handler;
327
+ if(sigaction(signals_to_ignore[i], &sa, NULL) == -1)
328
+ fprintf(stderr, "SPAWN: Failed to change signal handler for signal: %d.\n", signals_to_ignore[i]);
329
+ }
330
+
331
+ signals_unblock();
332
+
333
+ loop = uv_default_loop();
334
+ loop->data = NULL;
335
+
336
+ error = uv_pipe_init(loop, &server_pipe, 1);
337
+ if (error) {
338
+ fprintf(stderr, "uv_pipe_init(): %s\n", uv_strerror(error));
339
+ exit(error);
340
+ }
341
+ assert(server_pipe.ipc);
342
+
343
+ error = uv_pipe_open(&server_pipe, 0 /* UV_STDIN_FD */);
344
+ if (error) {
345
+ fprintf(stderr, "uv_pipe_open(): %s\n", uv_strerror(error));
346
+ exit(error);
347
+ }
348
+ avl_init_lock(&spawn_outstanding_exec_tree, spawn_exec_compare);
349
+
350
+ spawned_processes = 0;
351
+ assert(0 == uv_cond_init(&wait_children_cond));
352
+ assert(0 == uv_mutex_init(&wait_children_mutex));
353
+ child_waited_list = NULL;
354
+ error = uv_async_init(loop, &child_waited_async, child_waited_async_cb);
355
+ if (error) {
356
+ fprintf(stderr, "uv_async_init(): %s\n", uv_strerror(error));
357
+ exit(error);
358
+ }
359
+
360
+ error = uv_thread_create(&thread, wait_children, NULL);
361
+ if (error) {
362
+ fprintf(stderr, "uv_thread_create(): %s\n", uv_strerror(error));
363
+ exit(error);
364
+ }
365
+
366
+ prot_buffer_len = 0;
367
+ error = uv_read_start((uv_stream_t *)&server_pipe, on_read_alloc, on_pipe_read);
368
+ assert(error == 0);
369
+
370
+ while (!server_shutdown) {
371
+ uv_run(loop, UV_RUN_DEFAULT);
372
+ }
373
+ fprintf(stderr, "Shutting down spawn server loop complete.\n");
374
+ assert(0 == uv_loop_close(loop));
375
+
376
+ exit(0);
377
+}