/* * Copyright (C) Igor Sysoev * Copyright (C) NGINX, Inc. */ #include #include #include #include static nxt_int_t nxt_master_process_chan_create(nxt_task_t *task, nxt_cycle_t *cycle); static void nxt_master_process_title(void); static nxt_int_t nxt_master_start_worker_processes(nxt_task_t *task, nxt_cycle_t *cycle); static nxt_int_t nxt_master_create_worker_process(nxt_task_t *task, nxt_cycle_t *cycle); static void nxt_master_stop_previous_worker_processes(nxt_task_t *task, void *obj, void *data); static void nxt_master_process_sighup_handler(nxt_task_t *task, void *obj, void *data); static void nxt_master_process_new_cycle(nxt_task_t *task, nxt_cycle_t *cycle); static void nxt_master_process_sigterm_handler(nxt_task_t *task, void *obj, void *data); static void nxt_master_process_sigquit_handler(nxt_task_t *task, void *obj, void *data); static void nxt_master_process_sigusr1_handler(nxt_task_t *task, void *obj, void *data); static void nxt_master_process_sigusr2_handler(nxt_task_t *task, 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_task_t *task, void *obj, void *data); static void nxt_master_cleanup_worker_process(nxt_task_t *task, 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_task_t *task, nxt_cycle_t *cycle) { cycle->type = NXT_PROCESS_MASTER; if (nxt_master_process_chan_create(task, cycle) != NXT_OK) { return NXT_ERROR; } nxt_master_process_title(); return nxt_master_start_worker_processes(task, cycle); } static nxt_int_t nxt_master_process_chan_create(nxt_task_t *task, 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(task, 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_task_t *task, 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(task, cycle); if (ret != NXT_OK) { return ret; } } return NXT_OK; } static nxt_int_t nxt_master_create_worker_process(nxt_task_t *task, 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(task, proc->chan); nxt_process_new_chan(task, cycle, proc); return NXT_OK; } } static void nxt_master_process_sighup_handler(nxt_task_t *task, void *obj, void *data) { nxt_cycle_t *cycle; cycle = nxt_thread_cycle(); nxt_log(task, NXT_LOG_NOTICE, "signal %d (%s) recevied, %s", (int) (uintptr_t) obj, data, cycle->reconfiguring ? "ignored" : "reconfiguring"); if (!cycle->reconfiguring) { (void) nxt_cycle_create(task->thread, task, cycle, nxt_master_process_new_cycle, cycle->config_name); } } static void nxt_master_process_new_cycle(nxt_task_t *task, nxt_cycle_t *cycle) { nxt_thread_t *thr; thr = task->thread; nxt_debug(task, "new cycle"); /* A safe place to free the previous cycle. */ nxt_mem_pool_destroy(cycle->previous->mem_pool); switch (nxt_master_start_worker_processes(task, 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->engine->fast_work_queue; 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_task_t *task, 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(task, proc[i].chan, NXT_CHAN_MSG_QUIT, -1, 0, NULL); } } cycle->reconfiguring = 0; } void nxt_master_stop_worker_processes(nxt_task_t *task, nxt_cycle_t *cycle) { nxt_process_chan_write(task, cycle, NXT_CHAN_MSG_QUIT, -1, 0, NULL); } static void nxt_master_process_sigterm_handler(nxt_task_t *task, void *obj, void *data) { nxt_debug(task, "sigterm handler signo:%d (%s)", (int) (uintptr_t) obj, data); /* TODO: fast exit. */ nxt_exiting = 1; nxt_cycle_quit(task, NULL); } static void nxt_master_process_sigquit_handler(nxt_task_t *task, void *obj, void *data) { nxt_debug(task, "sigquit handler signo:%d (%s)", (int) (uintptr_t) obj, data); /* TODO: graceful exit. */ nxt_exiting = 1; nxt_cycle_quit(task, NULL); } static void nxt_master_process_sigusr1_handler(nxt_task_t *task, 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(task, NXT_LOG_NOTICE, "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(task, 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_task_t *task, 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(task, NXT_LOG_NOTICE, "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_task_t *task, void *obj, void *data) { int status; nxt_err_t err; nxt_pid_t pid; nxt_debug(task, "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(task, NXT_LOG_CRIT, "waitpid() failed: %E", err); return; } } nxt_debug(task, "waitpid(): %PI", pid); if (pid == 0) { return; } if (WTERMSIG(status)) { #ifdef WCOREDUMP nxt_log(task, NXT_LOG_CRIT, "process %PI exited on signal %d%s", pid, WTERMSIG(status), WCOREDUMP(status) ? " (core dumped)" : ""); #else nxt_log(task, NXT_LOG_CRIT, "process %PI exited on signal %d", pid, WTERMSIG(status)); #endif } else { nxt_trace(task, "process %PI exited with code %d", pid, WEXITSTATUS(status)); } nxt_master_cleanup_worker_process(task, pid); } } static void nxt_master_cleanup_worker_process(nxt_task_t *task, 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_debug(task, "processes %d", n); if (n == 2) { nxt_cycle_quit(task, cycle); } } else if (generation == cycle->process_generation) { (void) nxt_master_create_worker_process(task, cycle); } return; } } }