diff --git a/doc/nghttpx.h2r b/doc/nghttpx.h2r index 761735ca..be308a47 100644 --- a/doc/nghttpx.h2r +++ b/doc/nghttpx.h2r @@ -83,14 +83,18 @@ SIGUSR1 Reopen log files. SIGUSR2 + Fork and execute nghttpx. It will execute the binary in the same - path with same command-line arguments and environment variables. - After new process comes up, sending SIGQUIT to the original process - to perform hot swapping. The difference between SIGUSR2 + SIGQUIT - and SIGHUP is that former is usually used to execute new binary, and - the master process is newly spawned. On the other hand, the latter - just reloads configuration file, and the same master process - continues to exist. + path with same command-line arguments and environment variables. As + of nghttpx version 1.20.0, the new master process sends SIGQUIT to + the original master process when it is ready to serve requests. For + the earlier versions of nghttpx, user has to send SIGQUIT to the + original master process. + + The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former + is usually used to execute new binary, and the master process is + newly spawned. On the other hand, the latter just reloads + configuration file, and the same master process continues to exist. .. note:: diff --git a/doc/sources/nghttpx-howto.rst b/doc/sources/nghttpx-howto.rst index 91b9c377..e7549de8 100644 --- a/doc/sources/nghttpx-howto.rst +++ b/doc/sources/nghttpx-howto.rst @@ -229,12 +229,18 @@ Hot swapping nghttpx supports hot swapping using signals. The hot swapping in nghttpx is multi step process. First send USR2 signal to nghttpx process. It will do fork and execute new executable, using same -command-line arguments and environment variables. At this point, both -current and new processes can accept requests. To gracefully shutdown -current process, send QUIT signal to current nghttpx process. When -all existing frontend connections are done, the current process will -exit. At this point, only new nghttpx process exists and serves -incoming requests. +command-line arguments and environment variables. + +As of nghttpx version 1.20.0, that is all you have to do. The new +master process sends QUIT signal to the original process, when it is +ready to serve requests, to shut it down gracefully. + +For earlier versions of nghttpx, you have to do one more thing. At +this point, both current and new processes can accept requests. To +gracefully shutdown current process, send QUIT signal to current +nghttpx process. When all existing frontend connections are done, the +current process will exit. At this point, only new nghttpx process +exists and serves incoming requests. If you want to just reload configuration file without executing new binary, send SIGHUP to nghttpx master process. diff --git a/src/shrpx.cc b/src/shrpx.cc index 995d0782..c65a3d7c 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -123,6 +123,12 @@ constexpr auto ENV_UNIX_PATH = StringRef::from_lit("NGHTTP2_UNIX_PATH"); // descriptor. is a path to UNIX domain socket. constexpr auto ENV_ACCEPT_PREFIX = StringRef::from_lit("NGHTTPX_ACCEPT_"); +// This environment variable contains PID of the original master +// process, assuming that it created this master process as a result +// of SIGUSR2. The new master process is expected to send QUIT signal +// to the original master process to shut it down gracefully. +constexpr auto ENV_ORIG_PID = StringRef::from_lit("NGHTTPX_ORIG_PID"); + #ifndef _KERNEL_FASTOPEN #define _KERNEL_FASTOPEN // conditional define for TCP_FASTOPEN mostly on ubuntu @@ -456,7 +462,8 @@ void exec_binary() { auto &listenerconf = get_config()->conn.listener; - auto envp = make_unique(envlen + listenerconf.addrs.size() + 1); + // 2 for ENV_ORIG_PID and terminal nullptr. + auto envp = make_unique(envlen + listenerconf.addrs.size() + 2); size_t envidx = 0; std::vector fd_envs; @@ -479,6 +486,11 @@ void exec_binary() { envp[envidx++] = const_cast(fd_envs.back().c_str()); } + auto ipc_fd_str = ENV_ORIG_PID.str(); + ipc_fd_str += '='; + ipc_fd_str += util::utos(get_config()->pid); + envp[envidx++] = const_cast(ipc_fd_str.c_str()); + for (size_t i = 0; i < envlen; ++i) { auto env = StringRef{environ[i]}; if (util::starts_with(env, ENV_ACCEPT_PREFIX) || @@ -486,7 +498,8 @@ void exec_binary() { util::starts_with(env, ENV_LISTENER6_FD) || util::starts_with(env, ENV_PORT) || util::starts_with(env, ENV_UNIX_FD) || - util::starts_with(env, ENV_UNIX_PATH)) { + util::starts_with(env, ENV_UNIX_PATH) || + util::starts_with(env, ENV_ORIG_PID)) { continue; } @@ -1073,6 +1086,18 @@ void close_unused_inherited_addr(const std::vector &iaddrs) { } } // namespace +namespace { +// Returns the PID of the original master process from environment +// variable ENV_ORIG_PID. +pid_t get_orig_pid_from_env() { + auto s = getenv(ENV_ORIG_PID.c_str()); + if (s == nullptr) { + return -1; + } + return util::parse_uint(s); +} +} // namespace + namespace { int create_acceptor_socket(Config *config, std::vector &iaddrs) { std::array errbuf; @@ -1288,6 +1313,8 @@ int event_loop() { close_unused_inherited_addr(iaddrs); } + auto orig_pid = get_orig_pid_from_env(); + auto loop = ev_default_loop(config->ev_loop_flags); int ipc_fd; @@ -1311,6 +1338,12 @@ int event_loop() { // ready to serve requests shrpx_sd_notifyf(0, "READY=1"); + if (orig_pid != -1) { + LOG(NOTICE) << "Send QUIT signal to the original master process to tell " + "that we are ready to serve requests."; + kill(orig_pid, SIGQUIT); + } + ev_run(loop, 0); return 0;