summaryrefslogtreecommitdiffhomepage
path: root/src/nxt_master_process.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/nxt_master_process.c650
1 files changed, 650 insertions, 0 deletions
diff --git a/src/nxt_master_process.c b/src/nxt_master_process.c
new file mode 100644
index 00000000..df2934c0
--- /dev/null
+++ b/src/nxt_master_process.c
@@ -0,0 +1,650 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_main.h>
+#include <nxt_cycle.h>
+#include <nxt_process_chan.h>
+#include <nxt_master_process.h>
+
+
+static nxt_int_t nxt_master_process_chan_create(nxt_cycle_t *cycle);
+static void nxt_master_process_title(void);
+static nxt_int_t nxt_master_start_worker_processes(nxt_cycle_t *cycle);
+static nxt_int_t nxt_master_create_worker_process(nxt_cycle_t *cycle);
+static void nxt_master_stop_previous_worker_processes(nxt_thread_t *thr,
+ void *obj, void *data);
+static void nxt_master_process_sighup_handler(nxt_thread_t *thr, void *obj,
+ void *data);
+static void nxt_master_process_new_cycle(nxt_thread_t *thr, nxt_cycle_t *cycle);
+static void nxt_master_process_sigterm_handler(nxt_thread_t *thr, void *obj,
+ void *data);
+static void nxt_master_process_sigquit_handler(nxt_thread_t *thr, void *obj,
+ void *data);
+static void nxt_master_process_sigusr1_handler(nxt_thread_t *thr, void *obj,
+ void *data);
+static void nxt_master_process_sigusr2_handler(nxt_thread_t *thr, void *obj,
+ void *data);
+static char **nxt_master_process_upgrade_environment(nxt_cycle_t *cycle);
+static char **nxt_master_process_upgrade_environment_create(nxt_cycle_t *cycle);
+static void nxt_master_process_sigchld_handler(nxt_thread_t *thr, void *obj,
+ void *data);
+static void nxt_master_cleanup_worker_process(nxt_thread_t *thr, nxt_pid_t pid);
+
+
+const nxt_event_sig_t nxt_master_process_signals[] = {
+ nxt_event_signal(SIGHUP, nxt_master_process_sighup_handler),
+ nxt_event_signal(SIGINT, nxt_master_process_sigterm_handler),
+ nxt_event_signal(SIGQUIT, nxt_master_process_sigquit_handler),
+ nxt_event_signal(SIGTERM, nxt_master_process_sigterm_handler),
+ nxt_event_signal(SIGCHLD, nxt_master_process_sigchld_handler),
+ nxt_event_signal(SIGUSR1, nxt_master_process_sigusr1_handler),
+ nxt_event_signal(SIGUSR2, nxt_master_process_sigusr2_handler),
+ nxt_event_signal_end,
+};
+
+
+static nxt_bool_t nxt_exiting;
+
+
+nxt_int_t
+nxt_master_process_start(nxt_thread_t *thr, nxt_cycle_t *cycle)
+{
+ cycle->type = NXT_PROCESS_MASTER;
+
+ if (nxt_master_process_chan_create(cycle) != NXT_OK) {
+ return NXT_ERROR;
+ }
+
+ nxt_master_process_title();
+
+ return nxt_master_start_worker_processes(cycle);
+}
+
+
+static nxt_int_t
+nxt_master_process_chan_create(nxt_cycle_t *cycle)
+{
+ nxt_process_chan_t *proc;
+
+ proc = nxt_array_add(cycle->processes);
+ if (nxt_slow_path(proc == NULL)) {
+ return NXT_ERROR;
+ }
+
+ proc->pid = nxt_pid;
+ proc->engine = 0;
+
+ proc->chan = nxt_chan_create(0);
+ if (nxt_slow_path(proc->chan == NULL)) {
+ return NXT_ERROR;
+ }
+
+ /*
+ * A master process chan. A write chan is not closed
+ * since it should be inherited by worker processes.
+ */
+ nxt_chan_read_enable(nxt_thread(), proc->chan);
+
+ return NXT_OK;
+}
+
+
+static void
+nxt_master_process_title(void)
+{
+ u_char *p, *end;
+ nxt_uint_t i;
+ u_char title[2048];
+
+ end = title + sizeof(title);
+
+ p = nxt_sprintf(title, end, "nginman: master process %s",
+ nxt_process_argv[0]);
+
+ for (i = 1; nxt_process_argv[i] != NULL; i++) {
+ p = nxt_sprintf(p, end, " %s", nxt_process_argv[i]);
+ }
+
+ *p = '\0';
+
+ nxt_process_title((char *) title);
+}
+
+
+static nxt_int_t
+nxt_master_start_worker_processes(nxt_cycle_t *cycle)
+{
+ nxt_int_t ret;
+ nxt_uint_t n;
+
+ cycle->process_generation++;
+
+ n = cycle->worker_processes;
+
+ while (n-- != 0) {
+ ret = nxt_master_create_worker_process(cycle);
+
+ if (ret != NXT_OK) {
+ return ret;
+ }
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_master_create_worker_process(nxt_cycle_t *cycle)
+{
+ nxt_pid_t pid;
+ nxt_process_chan_t *proc;
+
+ proc = nxt_array_add(cycle->processes);
+ if (nxt_slow_path(proc == NULL)) {
+ return NXT_ERROR;
+ }
+
+ cycle->current_process = cycle->processes->nelts - 1;
+
+ proc->engine = 0;
+ proc->generation = cycle->process_generation;
+
+ proc->chan = nxt_chan_create(0);
+ if (nxt_slow_path(proc->chan == NULL)) {
+ return NXT_ERROR;
+ }
+
+ pid = nxt_process_create(nxt_worker_process_start, cycle,
+ "start worker process");
+
+ switch (pid) {
+
+ case -1:
+ return NXT_ERROR;
+
+ case 0:
+ /* A worker process, return to the event engine work queue loop. */
+ return NXT_AGAIN;
+
+ default:
+ /* The master process created a new process. */
+ proc->pid = pid;
+
+ nxt_chan_read_close(proc->chan);
+ nxt_chan_write_enable(nxt_thread(), proc->chan);
+
+ nxt_process_new_chan(cycle, proc);
+ return NXT_OK;
+ }
+}
+
+
+static void
+nxt_master_process_sighup_handler(nxt_thread_t *thr, void *obj, void *data)
+{
+ nxt_cycle_t *cycle;
+
+ cycle = nxt_thread_cycle();
+
+ nxt_log_error(NXT_LOG_NOTICE, thr->log, "signal %d (%s) recevied, %s",
+ (int) (uintptr_t) obj, data,
+ cycle->reconfiguring ? "ignored" : "reconfiguring");
+
+ if (!cycle->reconfiguring) {
+ (void) nxt_cycle_create(thr, cycle, nxt_master_process_new_cycle,
+ cycle->config_name, 0);
+ }
+}
+
+
+static void
+nxt_master_process_new_cycle(nxt_thread_t *thr, nxt_cycle_t *cycle)
+{
+ nxt_log_debug(thr->log, "new cycle");
+
+ /* A safe place to free the previous cycle. */
+ nxt_mem_pool_destroy(cycle->previous->mem_pool);
+
+ switch (nxt_master_start_worker_processes(cycle)) {
+
+ case NXT_OK:
+ /*
+ * The master process, allow old worker processes to accept new
+ * connections yet 500ms in parallel with new worker processes.
+ */
+ cycle->timer.handler = nxt_master_stop_previous_worker_processes;
+ cycle->timer.log = &nxt_main_log;
+ nxt_event_timer_ident(&cycle->timer, -1);
+
+ cycle->timer.work_queue = &thr->work_queue.main;
+
+ nxt_event_timer_add(thr->engine, &cycle->timer, 500);
+
+ return;
+
+ case NXT_ERROR:
+ /*
+ * The master process, one or more new worker processes
+ * could not be created, there is no fallback.
+ */
+ return;
+
+ default: /* NXT_AGAIN */
+ /* A worker process, return to the event engine work queue loop. */
+ return;
+ }
+}
+
+
+static void
+nxt_master_stop_previous_worker_processes(nxt_thread_t *thr, void *obj,
+ void *data)
+{
+ uint32_t generation;
+ nxt_uint_t i, n;
+ nxt_cycle_t *cycle;
+ nxt_process_chan_t *proc;
+
+ cycle = nxt_thread_cycle();
+
+ proc = cycle->processes->elts;
+ n = cycle->processes->nelts;
+
+ generation = cycle->process_generation - 1;
+
+ /* The proc[0] is the master process. */
+
+ for (i = 1; i < n; i++) {
+ if (proc[i].generation == generation) {
+ (void) nxt_chan_write(proc[i].chan, NXT_CHAN_MSG_QUIT, -1, 0, NULL);
+ }
+ }
+
+ cycle->reconfiguring = 0;
+}
+
+
+void
+nxt_master_stop_worker_processes(nxt_cycle_t *cycle)
+{
+ nxt_process_chan_write(cycle, NXT_CHAN_MSG_QUIT, -1, 0, NULL);
+}
+
+
+
+static void
+nxt_master_process_sigterm_handler(nxt_thread_t *thr, void *obj, void *data)
+{
+ nxt_log_debug(thr->log, "sigterm handler signo:%d (%s)",
+ (int) (uintptr_t) obj, data);
+
+ /* TODO: fast exit. */
+
+ nxt_exiting = 1;
+
+ nxt_cycle_quit(thr, NULL);
+}
+
+
+static void
+nxt_master_process_sigquit_handler(nxt_thread_t *thr, void *obj, void *data)
+{
+ nxt_log_debug(thr->log, "sigquit handler signo:%d (%s)",
+ (int) (uintptr_t) obj, data);
+
+ /* TODO: graceful exit. */
+
+ nxt_exiting = 1;
+
+ nxt_cycle_quit(thr, NULL);
+}
+
+
+static void
+nxt_master_process_sigusr1_handler(nxt_thread_t *thr, void *obj, void *data)
+{
+ nxt_int_t ret;
+ nxt_uint_t n;
+ nxt_file_t *file, *new_file;
+ nxt_cycle_t *cycle;
+ nxt_array_t *new_files;
+ nxt_mem_pool_t *mp;
+
+ nxt_log_error(NXT_LOG_NOTICE, thr->log, "signal %d (%s) recevied, %s",
+ (int) (uintptr_t) obj, data, "log files rotation");
+
+ mp = nxt_mem_pool_create(1024);
+ if (mp == NULL) {
+ return;
+ }
+
+ cycle = nxt_thread_cycle();
+
+ n = nxt_list_nelts(cycle->log_files);
+
+ new_files = nxt_array_create(mp, n, sizeof(nxt_file_t));
+ if (new_files == NULL) {
+ nxt_mem_pool_destroy(mp);
+ return;
+ }
+
+ nxt_list_each(file, cycle->log_files) {
+
+ /* This allocation cannot fail. */
+ new_file = nxt_array_add(new_files);
+
+ new_file->name = file->name;
+ new_file->fd = NXT_FILE_INVALID;
+ new_file->log_level = NXT_LOG_CRIT;
+
+ ret = nxt_file_open(new_file, NXT_FILE_APPEND, NXT_FILE_CREATE_OR_OPEN,
+ NXT_FILE_OWNER_ACCESS);
+
+ if (ret != NXT_OK) {
+ goto fail;
+ }
+
+ } nxt_list_loop;
+
+ new_file = new_files->elts;
+
+ ret = nxt_file_stderr(&new_file[0]);
+
+ if (ret == NXT_OK) {
+ n = 0;
+
+ nxt_list_each(file, cycle->log_files) {
+
+ nxt_process_chan_change_log_file(cycle, n, new_file[n].fd);
+ /*
+ * The old log file descriptor must be closed at the moment
+ * when no other threads use it. dup2() allows to use the
+ * old file descriptor for new log file. This change is
+ * performed atomically in the kernel.
+ */
+ (void) nxt_file_redirect(file, new_file[n].fd);
+
+ n++;
+
+ } nxt_list_loop;
+
+ nxt_mem_pool_destroy(mp);
+ return;
+ }
+
+fail:
+
+ new_file = new_files->elts;
+ n = new_files->nelts;
+
+ while (n != 0) {
+ if (new_file->fd != NXT_FILE_INVALID) {
+ nxt_file_close(new_file);
+ }
+
+ new_file++;
+ n--;
+ }
+
+ nxt_mem_pool_destroy(mp);
+}
+
+
+static void
+nxt_master_process_sigusr2_handler(nxt_thread_t *thr, void *obj, void *data)
+{
+ char **env;
+ nxt_int_t ret;
+ nxt_pid_t pid, ppid;
+ nxt_bool_t ignore;
+ nxt_cycle_t *cycle;
+
+ cycle = nxt_thread_cycle();
+
+ /* Is upgrade or reconfiguring in progress? */
+ ignore = (cycle->new_binary != 0) || cycle->reconfiguring;
+
+ ppid = getppid();
+
+ if (ppid == nxt_ppid && ppid != 1) {
+ /*
+ * Ignore the upgrade signal in a new master process if an old
+ * master process is still running. After the old process's exit
+ * getppid() will return 1 (init process pid) or pid of zsched (zone
+ * scheduler) if the processes run in Solaris zone. There is little
+ * race condition between the parent process exit and getting getppid()
+ * for the very start of the new master process execution, so init or
+ * zsched pid may be stored in nxt_ppid. For this reason pid 1 is
+ * tested explicitly. There is no workaround for this race condition
+ * in Solaris zons. To eliminate this race condition in Solaris
+ * zone the old master process should be quit only when both
+ * "nginman.pid.oldbin" (created by the old master process) and
+ * "nginman.pid" (created by the new master process) files exists.
+ */
+ ignore = 1;
+ }
+
+ nxt_log_error(NXT_LOG_NOTICE, thr->log,
+ "signal %d (%s) recevied, %s, parent pid: %PI",
+ (int) (uintptr_t) obj, data,
+ ignore ? "ignored" : "online binary file upgrade", ppid);
+
+ if (ignore) {
+ return;
+ }
+
+ env = nxt_master_process_upgrade_environment(cycle);
+ if (nxt_slow_path(env == NULL)) {
+ return;
+ }
+
+ cycle->new_binary = -1;
+
+ ret = nxt_cycle_pid_file_create(cycle->oldbin_file, 0);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ goto fail;
+ }
+
+ pid = nxt_process_execute(nxt_process_argv[0], nxt_process_argv, env);
+
+ if (pid == -1) {
+ cycle->new_binary = 0;
+ (void) nxt_file_delete(cycle->oldbin_file);
+
+ } else {
+ cycle->new_binary = pid;
+ }
+
+fail:
+
+ /* Zero slot is NGINX variable slot, all other slots must not be free()d. */
+ nxt_free(env[0]);
+ nxt_free(env);
+}
+
+
+static char **
+nxt_master_process_upgrade_environment(nxt_cycle_t *cycle)
+{
+ size_t len;
+ char **env;
+ u_char *p, *end;
+ nxt_uint_t n;
+ nxt_listen_socket_t *ls;
+
+ env = nxt_master_process_upgrade_environment_create(cycle);
+ if (nxt_slow_path(env == NULL)) {
+ return NULL;
+ }
+
+ ls = cycle->listen_sockets->elts;
+ n = cycle->listen_sockets->nelts;
+
+ len = sizeof("NGINX=") + n * (NXT_INT_T_LEN + 1);
+
+ p = nxt_malloc(len);
+
+ if (nxt_slow_path(p == NULL)) {
+ nxt_free(env);
+ return NULL;
+ }
+
+ env[0] = (char *) p;
+ end = p + len;
+
+ p = nxt_cpymem(p, "NGINX=", sizeof("NGINX=") - 1);
+
+ do {
+ p = nxt_sprintf(p, end, "%ud;", ls->socket);
+
+ ls++;
+ n--;
+ } while (n != 0);
+
+ *p = '\0';
+
+ return env;
+}
+
+
+static char **
+nxt_master_process_upgrade_environment_create(nxt_cycle_t *cycle)
+{
+ char **env;
+ nxt_uint_t n;
+
+ /* 2 is for "NGINX" variable and the last NULL slot. */
+ n = 2;
+
+#if (NXT_SETPROCTITLE_ARGV)
+ n++;
+#endif
+
+ env = nxt_malloc(n * sizeof(char *));
+ if (nxt_slow_path(env == NULL)) {
+ return NULL;
+ }
+
+ /* Zero slot is reserved for "NGINX" variable. */
+ n = 1;
+
+ /* TODO: copy env values */
+
+#if (NXT_SETPROCTITLE_ARGV)
+
+ /* 300 spare bytes for new process title. */
+ env[n++] = (char *)
+ "SPARE=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
+
+#endif
+
+ env[n] = NULL;
+
+ return env;
+}
+
+
+static void
+nxt_master_process_sigchld_handler(nxt_thread_t *thr, void *obj, void *data)
+{
+ int status;
+ nxt_err_t err;
+ nxt_pid_t pid;
+
+ nxt_log_debug(thr->log, "sigchld handler signo:%d (%s)",
+ (int) (uintptr_t) obj, data);
+
+ for ( ;; ) {
+ pid = waitpid(-1, &status, WNOHANG);
+
+ if (pid == -1) {
+
+ switch (err = nxt_errno) {
+
+ case NXT_ECHILD:
+ return;
+
+ case NXT_EINTR:
+ continue;
+
+ default:
+ nxt_log_alert(thr->log, "waitpid() failed: %E", err);
+ return;
+ }
+ }
+
+ nxt_log_debug(thr->log, "waitpid(): %PI", pid);
+
+ if (pid == 0) {
+ return;
+ }
+
+ if (WTERMSIG(status)) {
+#ifdef WCOREDUMP
+ nxt_log_alert(thr->log, "process %PI exited on signal %d%s",
+ pid, WTERMSIG(status),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+#else
+ nxt_log_alert(thr->log, "process %PI exited on signal %d",
+ pid, WTERMSIG(status));
+#endif
+
+ } else {
+ nxt_log_error(NXT_LOG_NOTICE, thr->log,
+ "process %PI exited with code %d",
+ pid, WEXITSTATUS(status));
+ }
+
+ nxt_master_cleanup_worker_process(thr, pid);
+ }
+}
+
+
+static void
+nxt_master_cleanup_worker_process(nxt_thread_t *thr, nxt_pid_t pid)
+{
+ nxt_uint_t i, n, generation;
+ nxt_cycle_t *cycle;
+ nxt_process_chan_t *proc;
+
+ cycle = nxt_thread_cycle();
+
+ if (cycle->new_binary == pid) {
+ cycle->new_binary = 0;
+
+ (void) nxt_file_rename(cycle->oldbin_file, cycle->pid_file);
+ return;
+ }
+
+ proc = cycle->processes->elts;
+ n = cycle->processes->nelts;
+
+ for (i = 0; i < n; i++) {
+
+ if (pid == proc[i].pid) {
+ generation = proc[i].generation;
+
+ nxt_array_remove(cycle->processes, &proc[i]);
+
+ if (nxt_exiting) {
+ nxt_log_debug(thr->log, "processes %d", n);
+
+ if (n == 2) {
+ nxt_cycle_quit(thr, cycle);
+ }
+
+ } else if (generation == cycle->process_generation) {
+ (void) nxt_master_create_worker_process(cycle);
+ }
+
+ return;
+ }
+ }
+}