nghttpx: Add hot deploy feature
nghttpx supports hot deploy feature using signals. The host deploy 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.
This commit is contained in:
parent
8aa6580d89
commit
bf13d91264
|
@ -262,3 +262,16 @@ used in frontend, and host is replaced with which appears in
|
||||||
precedence. If the above conditions are not met with the host value
|
precedence. If the above conditions are not met with the host value
|
||||||
in :authority header field, rewrite is retried with the value in host
|
in :authority header field, rewrite is retried with the value in host
|
||||||
header field.
|
header field.
|
||||||
|
|
||||||
|
Hot deploy
|
||||||
|
----------
|
||||||
|
|
||||||
|
nghttpx supports hot deploy feature using signals. The host deploy 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.
|
||||||
|
|
259
src/shrpx.cc
259
src/shrpx.cc
|
@ -36,6 +36,7 @@
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
@ -55,18 +56,32 @@
|
||||||
#include "shrpx_listen_handler.h"
|
#include "shrpx_listen_handler.h"
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_worker_config.h"
|
#include "shrpx_worker_config.h"
|
||||||
|
#include "shrpx_worker.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "app_helper.h"
|
#include "app_helper.h"
|
||||||
#include "ssl.h"
|
#include "ssl.h"
|
||||||
|
|
||||||
|
extern char **environ;
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const int REOPEN_LOG_SIGNAL = SIGUSR1;
|
const int REOPEN_LOG_SIGNAL = SIGUSR1;
|
||||||
|
const int EXEC_BINARY_SIGNAL = SIGUSR2;
|
||||||
|
const int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Environment variables to tell new binary the listening socket's
|
||||||
|
// file descriptors. They are not close-on-exec.
|
||||||
|
#define ENV_LISTENER4_FD "NGHTTPX_LISTENER4_FD"
|
||||||
|
#define ENV_LISTENER6_FD "NGHTTPX_LISTENER6_FD"
|
||||||
|
|
||||||
|
// Environment variable to tell new binary the port number the current
|
||||||
|
// binary is listening to.
|
||||||
|
#define ENV_PORT "NGHTTPX_PORT"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void ssl_acceptcb(evconnlistener *listener, int fd,
|
void ssl_acceptcb(evconnlistener *listener, int fd,
|
||||||
sockaddr *addr, int addrlen, void *arg)
|
sockaddr *addr, int addrlen, void *arg)
|
||||||
|
@ -139,9 +154,50 @@ void evlistener_errorcb(evconnlistener *listener, void *ptr)
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
evconnlistener* new_evlistener(ListenHandler *handler, int fd)
|
||||||
|
{
|
||||||
|
auto evlistener = evconnlistener_new
|
||||||
|
(handler->get_evbase(),
|
||||||
|
ssl_acceptcb,
|
||||||
|
handler,
|
||||||
|
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
|
||||||
|
get_config()->backlog,
|
||||||
|
fd);
|
||||||
|
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
||||||
|
return evlistener;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
evconnlistener* create_evlistener(ListenHandler *handler, int family)
|
evconnlistener* create_evlistener(ListenHandler *handler, int family)
|
||||||
{
|
{
|
||||||
|
{
|
||||||
|
auto envfd = getenv(family == AF_INET ?
|
||||||
|
ENV_LISTENER4_FD : ENV_LISTENER6_FD);
|
||||||
|
auto envport = getenv(ENV_PORT);
|
||||||
|
|
||||||
|
if(envfd && envport) {
|
||||||
|
auto fd = strtoul(envfd, nullptr, 10);
|
||||||
|
auto port = strtoul(envport, nullptr, 10);
|
||||||
|
|
||||||
|
// Only do this iff NGHTTPX_PORT == get_config()->port.
|
||||||
|
// Otherwise, close fd, and create server socket as usual.
|
||||||
|
|
||||||
|
if(port == get_config()->port) {
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Listening on port " << get_config()->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_evlistener(handler, fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(WARNING) << "Port was changed between old binary (" << port
|
||||||
|
<< ") and new binary (" << get_config()->port << ")";
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addrinfo hints;
|
addrinfo hints;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
int rv;
|
int rv;
|
||||||
|
@ -222,15 +278,7 @@ evconnlistener* create_evlistener(ListenHandler *handler, int family)
|
||||||
LOG(INFO) << "Listening on " << host << ", port " << get_config()->port;
|
LOG(INFO) << "Listening on " << host << ", port " << get_config()->port;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto evlistener = evconnlistener_new
|
return new_evlistener(handler, fd);
|
||||||
(handler->get_evbase(),
|
|
||||||
ssl_acceptcb,
|
|
||||||
handler,
|
|
||||||
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
|
|
||||||
get_config()->backlog,
|
|
||||||
fd);
|
|
||||||
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
|
||||||
return evlistener;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -285,6 +333,122 @@ void reopen_log_signal_cb(evutil_socket_t sig, short events, void *arg)
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg)
|
||||||
|
{
|
||||||
|
auto listener_handler = static_cast<ListenHandler*>(arg);
|
||||||
|
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Executing new binary";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pid = fork();
|
||||||
|
|
||||||
|
if(pid == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(ERROR) << "fork() failed errno=" << error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pid != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto exec_path = util::get_exec_path(get_config()->argc,
|
||||||
|
get_config()->argv,
|
||||||
|
get_config()->cwd);
|
||||||
|
|
||||||
|
if(!exec_path) {
|
||||||
|
LOG(ERROR) << "Could not resolve the executable path";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto argv =
|
||||||
|
static_cast<char**>(malloc(sizeof(char*) * (get_config()->argc + 1)));
|
||||||
|
|
||||||
|
argv[0] = exec_path;
|
||||||
|
for(int i = 1; i < get_config()->argc; ++i) {
|
||||||
|
argv[i] = strdup(get_config()->argv[i]);
|
||||||
|
}
|
||||||
|
argv[get_config()->argc] = nullptr;
|
||||||
|
|
||||||
|
size_t envlen = 0;
|
||||||
|
for(char **p = environ; *p; ++p, ++envlen);
|
||||||
|
// 3 for missing fd4, fd6 and port.
|
||||||
|
auto envp = static_cast<char**>(malloc(sizeof(char*) * (envlen + 3 + 1)));
|
||||||
|
size_t envidx = 0;
|
||||||
|
|
||||||
|
auto evlistener4 = listener_handler->get_evlistener4();
|
||||||
|
if(evlistener4) {
|
||||||
|
std::string fd4 = ENV_LISTENER4_FD "=";
|
||||||
|
fd4 += util::utos(evconnlistener_get_fd(evlistener4));
|
||||||
|
envp[envidx++] = strdup(fd4.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto evlistener6 = listener_handler->get_evlistener6();
|
||||||
|
if(evlistener6) {
|
||||||
|
std::string fd6 = ENV_LISTENER6_FD "=";
|
||||||
|
fd6 += util::utos(evconnlistener_get_fd(evlistener6));
|
||||||
|
envp[envidx++] = strdup(fd6.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string port = ENV_PORT "=";
|
||||||
|
port += util::utos(get_config()->port);
|
||||||
|
envp[envidx++] = strdup(port.c_str());
|
||||||
|
|
||||||
|
for(size_t i = 0; i < envlen; ++i) {
|
||||||
|
if(strcmp(ENV_LISTENER4_FD, environ[i]) == 0 ||
|
||||||
|
strcmp(ENV_LISTENER6_FD, environ[i]) == 0 ||
|
||||||
|
strcmp(ENV_PORT, environ[i]) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
envp[envidx++] = environ[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
envp[envidx++] = nullptr;
|
||||||
|
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "cmdline";
|
||||||
|
for(int i = 0; argv[i]; ++i) {
|
||||||
|
LOG(INFO) << i << ": " << argv[i];
|
||||||
|
}
|
||||||
|
LOG(INFO) << "environ";
|
||||||
|
for(int i = 0; envp[i]; ++i) {
|
||||||
|
LOG(INFO) << i << ": " << envp[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(execve(argv[0], argv, envp) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(ERROR) << "execve failed: errno=" << error;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void graceful_shutdown_signal_cb(evutil_socket_t sig, short events, void *arg)
|
||||||
|
{
|
||||||
|
auto listener_handler = static_cast<ListenHandler*>(arg);
|
||||||
|
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Graceful shutdown signal received";
|
||||||
|
}
|
||||||
|
|
||||||
|
listener_handler->disable_evlistener();
|
||||||
|
|
||||||
|
// After disabling accepting new connection, disptach incoming
|
||||||
|
// connection in backlog.
|
||||||
|
|
||||||
|
listener_handler->accept_pending_connection();
|
||||||
|
|
||||||
|
worker_config.graceful_shutdown = true;
|
||||||
|
|
||||||
|
listener_handler->graceful_shutdown_worker();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::unique_ptr<std::string> generate_time()
|
std::unique_ptr<std::string> generate_time()
|
||||||
{
|
{
|
||||||
|
@ -310,7 +474,18 @@ std::unique_ptr<std::string> generate_time()
|
||||||
namespace {
|
namespace {
|
||||||
void refresh_cb(evutil_socket_t sig, short events, void *arg)
|
void refresh_cb(evutil_socket_t sig, short events, void *arg)
|
||||||
{
|
{
|
||||||
|
auto listener_handler = static_cast<ListenHandler*>(arg);
|
||||||
|
auto worker_stat = listener_handler->get_worker_stat();
|
||||||
|
|
||||||
mod_config()->cached_time = generate_time();
|
mod_config()->cached_time = generate_time();
|
||||||
|
|
||||||
|
// In multi threaded mode (get_config()->num_worker > 1), we have to
|
||||||
|
// wait for event notification to workers to finish.
|
||||||
|
if(get_config()->num_worker == 1 &&
|
||||||
|
worker_config.graceful_shutdown &&
|
||||||
|
(!worker_stat || worker_stat->num_connections == 0)) {
|
||||||
|
event_base_loopbreak(listener_handler->get_evbase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -358,6 +533,9 @@ int event_loop()
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listener_handler->set_evlistener4(evlistener4);
|
||||||
|
listener_handler->set_evlistener6(evlistener6);
|
||||||
|
|
||||||
// ListenHandler loads private key, and we listen on a priveleged port.
|
// ListenHandler loads private key, and we listen on a priveleged port.
|
||||||
// After that, we drop the root privileges if needed.
|
// After that, we drop the root privileges if needed.
|
||||||
drop_privileges();
|
drop_privileges();
|
||||||
|
@ -366,10 +544,11 @@ int event_loop()
|
||||||
sigset_t signals;
|
sigset_t signals;
|
||||||
sigemptyset(&signals);
|
sigemptyset(&signals);
|
||||||
sigaddset(&signals, REOPEN_LOG_SIGNAL);
|
sigaddset(&signals, REOPEN_LOG_SIGNAL);
|
||||||
|
sigaddset(&signals, EXEC_BINARY_SIGNAL);
|
||||||
|
sigaddset(&signals, GRACEFUL_SHUTDOWN_SIGNAL);
|
||||||
rv = pthread_sigmask(SIG_BLOCK, &signals, nullptr);
|
rv = pthread_sigmask(SIG_BLOCK, &signals, nullptr);
|
||||||
if(rv != 0) {
|
if(rv != 0) {
|
||||||
LOG(ERROR) << "Blocking REOPEN_LOG_SIGNAL failed: " << strerror(rv);
|
LOG(ERROR) << "Blocking signals failed: " << strerror(rv);
|
||||||
}
|
}
|
||||||
#endif // !NOTHREADS
|
#endif // !NOTHREADS
|
||||||
|
|
||||||
|
@ -382,7 +561,7 @@ int event_loop()
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
rv = pthread_sigmask(SIG_UNBLOCK, &signals, nullptr);
|
rv = pthread_sigmask(SIG_UNBLOCK, &signals, nullptr);
|
||||||
if(rv != 0) {
|
if(rv != 0) {
|
||||||
LOG(ERROR) << "Unblocking REOPEN_LOG_SIGNAL failed: " << strerror(rv);
|
LOG(ERROR) << "Unblocking signals failed: " << strerror(rv);
|
||||||
}
|
}
|
||||||
#endif // !NOTHREADS
|
#endif // !NOTHREADS
|
||||||
|
|
||||||
|
@ -399,8 +578,31 @@ int event_loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto exec_binary_signal_event = evsignal_new(evbase, EXEC_BINARY_SIGNAL,
|
||||||
|
exec_binary_signal_cb,
|
||||||
|
listener_handler);
|
||||||
|
rv = event_add(exec_binary_signal_event, nullptr);
|
||||||
|
|
||||||
|
if(rv == -1) {
|
||||||
|
LOG(FATAL) << "event_add for exec_binary_signal_event failed";
|
||||||
|
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto graceful_shutdown_signal_event = evsignal_new
|
||||||
|
(evbase, GRACEFUL_SHUTDOWN_SIGNAL, graceful_shutdown_signal_cb,
|
||||||
|
listener_handler);
|
||||||
|
|
||||||
|
rv = event_add(graceful_shutdown_signal_event, nullptr);
|
||||||
|
|
||||||
|
if(rv == -1) {
|
||||||
|
LOG(FATAL) << "event_add for graceful_shutdown_signal_event failed";
|
||||||
|
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
auto refresh_event = event_new(evbase, -1, EV_PERSIST, refresh_cb,
|
auto refresh_event = event_new(evbase, -1, EV_PERSIST, refresh_cb,
|
||||||
nullptr);
|
listener_handler);
|
||||||
|
|
||||||
if(!refresh_event) {
|
if(!refresh_event) {
|
||||||
LOG(ERROR) << "event_new failed";
|
LOG(ERROR) << "event_new failed";
|
||||||
|
@ -422,9 +624,17 @@ int event_loop()
|
||||||
}
|
}
|
||||||
event_base_loop(evbase, 0);
|
event_base_loop(evbase, 0);
|
||||||
|
|
||||||
|
listener_handler->join_worker();
|
||||||
|
|
||||||
if(refresh_event) {
|
if(refresh_event) {
|
||||||
event_free(refresh_event);
|
event_free(refresh_event);
|
||||||
}
|
}
|
||||||
|
if(graceful_shutdown_signal_event) {
|
||||||
|
event_free(graceful_shutdown_signal_event);
|
||||||
|
}
|
||||||
|
if(exec_binary_signal_event) {
|
||||||
|
event_free(exec_binary_signal_event);
|
||||||
|
}
|
||||||
if(reopen_log_signal_event) {
|
if(reopen_log_signal_event) {
|
||||||
event_free(reopen_log_signal_event);
|
event_free(reopen_log_signal_event);
|
||||||
}
|
}
|
||||||
|
@ -570,6 +780,8 @@ void fill_default_config()
|
||||||
mod_config()->tls_proto_mask = 0;
|
mod_config()->tls_proto_mask = 0;
|
||||||
mod_config()->cached_time = generate_time();
|
mod_config()->cached_time = generate_time();
|
||||||
mod_config()->no_location_rewrite = false;
|
mod_config()->no_location_rewrite = false;
|
||||||
|
mod_config()->argc = 0;
|
||||||
|
mod_config()->argv = nullptr;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -933,6 +1145,23 @@ int main(int argc, char **argv)
|
||||||
create_config();
|
create_config();
|
||||||
fill_default_config();
|
fill_default_config();
|
||||||
|
|
||||||
|
// We have to copy argv, since getopt_long may change its content.
|
||||||
|
mod_config()->argc = argc;
|
||||||
|
mod_config()->argv = new char*[argc];
|
||||||
|
|
||||||
|
for(int i = 0; i < argc; ++i) {
|
||||||
|
mod_config()->argv[i] = strdup(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
char cwd[PATH_MAX];
|
||||||
|
|
||||||
|
mod_config()->cwd = getcwd(cwd, sizeof(cwd));
|
||||||
|
if(mod_config()->cwd == nullptr) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(FATAL) << "failed to get current working directory: errno=" << error;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<const char*, const char*> > cmdcfgs;
|
std::vector<std::pair<const char*, const char*> > cmdcfgs;
|
||||||
while(1) {
|
while(1) {
|
||||||
static int flag = 0;
|
static int flag = 0;
|
||||||
|
@ -1488,6 +1717,10 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
event_loop();
|
event_loop();
|
||||||
|
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Shutdown momentarily";
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "shrpx_http2_downstream_connection.h"
|
#include "shrpx_http2_downstream_connection.h"
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
|
#include "shrpx_worker_config.h"
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
#include "shrpx_spdy_upstream.h"
|
#include "shrpx_spdy_upstream.h"
|
||||||
#endif // HAVE_SPDYLAY
|
#endif // HAVE_SPDYLAY
|
||||||
|
@ -266,6 +267,12 @@ ClientHandler::~ClientHandler()
|
||||||
|
|
||||||
--worker_stat_->num_connections;
|
--worker_stat_->num_connections;
|
||||||
|
|
||||||
|
// TODO If backend is http/2, and it is in CONNECTED state, signal
|
||||||
|
// it and make it loopbreak when output is zero.
|
||||||
|
if(worker_config.graceful_shutdown && worker_stat_->num_connections == 0) {
|
||||||
|
event_base_loopbreak(get_evbase());
|
||||||
|
}
|
||||||
|
|
||||||
if(reneg_shutdown_timerev_) {
|
if(reneg_shutdown_timerev_) {
|
||||||
event_free(reneg_shutdown_timerev_);
|
event_free(reneg_shutdown_timerev_);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,9 @@ FILE* open_file_for_write(const char *filename)
|
||||||
LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: "
|
LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: "
|
||||||
<< strerror(errno);
|
<< strerror(errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evutil_make_socket_closeonexec(fileno(f));
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -206,6 +206,8 @@ struct Config {
|
||||||
FILE *http2_upstream_dump_request_header;
|
FILE *http2_upstream_dump_request_header;
|
||||||
FILE *http2_upstream_dump_response_header;
|
FILE *http2_upstream_dump_response_header;
|
||||||
nghttp2_option *http2_option;
|
nghttp2_option *http2_option;
|
||||||
|
char **argv;
|
||||||
|
char *cwd;
|
||||||
size_t downstream_addrlen;
|
size_t downstream_addrlen;
|
||||||
size_t num_worker;
|
size_t num_worker;
|
||||||
size_t http2_max_concurrent_streams;
|
size_t http2_max_concurrent_streams;
|
||||||
|
@ -232,6 +234,7 @@ struct Config {
|
||||||
shrpx_proto downstream_proto;
|
shrpx_proto downstream_proto;
|
||||||
int syslog_facility;
|
int syslog_facility;
|
||||||
int backlog;
|
int backlog;
|
||||||
|
int argc;
|
||||||
uid_t uid;
|
uid_t uid;
|
||||||
gid_t gid;
|
gid_t gid;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
@ -197,8 +198,10 @@ int Http2Session::init_notification()
|
||||||
SSLOG(FATAL, this) << "socketpair() failed: errno=" << errno;
|
SSLOG(FATAL, this) << "socketpair() failed: errno=" << errno;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
evutil_make_socket_nonblocking(sockpair[0]);
|
for(int i = 0; i < 2; ++i) {
|
||||||
evutil_make_socket_nonblocking(sockpair[1]);
|
evutil_make_socket_nonblocking(sockpair[i]);
|
||||||
|
evutil_make_socket_closeonexec(sockpair[i]);
|
||||||
|
}
|
||||||
wrbev_ = bufferevent_socket_new(evbase_, sockpair[0],
|
wrbev_ = bufferevent_socket_new(evbase_, sockpair[0],
|
||||||
BEV_OPT_CLOSE_ON_FREE|
|
BEV_OPT_CLOSE_ON_FREE|
|
||||||
BEV_OPT_DEFER_CALLBACKS);
|
BEV_OPT_DEFER_CALLBACKS);
|
||||||
|
@ -459,6 +462,13 @@ int Http2Session::initiate_connection()
|
||||||
}
|
}
|
||||||
// If state_ == PROXY_CONNECTED, we has connected to the proxy
|
// If state_ == PROXY_CONNECTED, we has connected to the proxy
|
||||||
// using fd_ and tunnel has been established.
|
// using fd_ and tunnel has been established.
|
||||||
|
if(state_ == DISCONNECTED) {
|
||||||
|
assert(fd_ == -1);
|
||||||
|
|
||||||
|
fd_ = socket(get_config()->downstream_addr.storage.ss_family,
|
||||||
|
SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||||
|
}
|
||||||
|
|
||||||
bev_ = bufferevent_openssl_socket_new(evbase_, fd_, ssl_,
|
bev_ = bufferevent_openssl_socket_new(evbase_, fd_, ssl_,
|
||||||
BUFFEREVENT_SSL_CONNECTING,
|
BUFFEREVENT_SSL_CONNECTING,
|
||||||
BEV_OPT_DEFER_CALLBACKS);
|
BEV_OPT_DEFER_CALLBACKS);
|
||||||
|
@ -471,33 +481,39 @@ int Http2Session::initiate_connection()
|
||||||
// TODO maybe not thread-safe?
|
// TODO maybe not thread-safe?
|
||||||
const_cast<sockaddr*>(&get_config()->downstream_addr.sa),
|
const_cast<sockaddr*>(&get_config()->downstream_addr.sa),
|
||||||
get_config()->downstream_addrlen);
|
get_config()->downstream_addrlen);
|
||||||
} else if(state_ == DISCONNECTED) {
|
|
||||||
// Without TLS and proxy.
|
|
||||||
bev_ = bufferevent_socket_new(evbase_, -1, BEV_OPT_DEFER_CALLBACKS);
|
|
||||||
if(!bev_) {
|
|
||||||
SSLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
|
||||||
return SHRPX_ERR_NETWORK;
|
|
||||||
}
|
|
||||||
rv = bufferevent_socket_connect
|
|
||||||
(bev_,
|
|
||||||
const_cast<sockaddr*>(&get_config()->downstream_addr.sa),
|
|
||||||
get_config()->downstream_addrlen);
|
|
||||||
} else {
|
} else {
|
||||||
assert(state_ == PROXY_CONNECTED);
|
if(state_ == DISCONNECTED) {
|
||||||
// Without TLS but with proxy.
|
// Without TLS and proxy.
|
||||||
|
assert(fd_ == -1);
|
||||||
|
|
||||||
|
fd_ = socket(get_config()->downstream_addr.storage.ss_family,
|
||||||
|
SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||||
|
}
|
||||||
|
|
||||||
bev_ = bufferevent_socket_new(evbase_, fd_, BEV_OPT_DEFER_CALLBACKS);
|
bev_ = bufferevent_socket_new(evbase_, fd_, BEV_OPT_DEFER_CALLBACKS);
|
||||||
if(!bev_) {
|
if(!bev_) {
|
||||||
SSLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
SSLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
// Connection already established.
|
|
||||||
eventcb(bev_, BEV_EVENT_CONNECTED, this);
|
|
||||||
// eventcb() has no return value. Check state_ to whether it was
|
|
||||||
// failed or not.
|
|
||||||
if(state_ == DISCONNECTED) {
|
if(state_ == DISCONNECTED) {
|
||||||
return -1;
|
rv = bufferevent_socket_connect
|
||||||
|
(bev_,
|
||||||
|
const_cast<sockaddr*>(&get_config()->downstream_addr.sa),
|
||||||
|
get_config()->downstream_addrlen);
|
||||||
|
} else {
|
||||||
|
// Without TLS but with proxy.
|
||||||
|
|
||||||
|
// Connection already established.
|
||||||
|
eventcb(bev_, BEV_EVENT_CONNECTED, this);
|
||||||
|
// eventcb() has no return value. Check state_ to whether it was
|
||||||
|
// failed or not.
|
||||||
|
if(state_ == DISCONNECTED) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rv != 0) {
|
if(rv != 0) {
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,11 +77,17 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream)
|
||||||
auto upstream = downstream->get_upstream();
|
auto upstream = downstream->get_upstream();
|
||||||
if(!bev_) {
|
if(!bev_) {
|
||||||
auto evbase = client_handler_->get_evbase();
|
auto evbase = client_handler_->get_evbase();
|
||||||
|
|
||||||
|
auto fd = socket(get_config()->downstream_addr.storage.ss_family,
|
||||||
|
SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||||
|
|
||||||
bev_ = bufferevent_socket_new
|
bev_ = bufferevent_socket_new
|
||||||
(evbase, -1,
|
(evbase, fd,
|
||||||
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||||
if(!bev_) {
|
if(!bev_) {
|
||||||
DCLOG(INFO, this) << "bufferevent_socket_new() failed";
|
DCLOG(INFO, this) << "bufferevent_socket_new() failed";
|
||||||
|
close(fd);
|
||||||
|
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
int rv = bufferevent_socket_connect
|
int rv = bufferevent_socket_connect
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <system_error>
|
|
||||||
|
|
||||||
#include <event2/bufferevent_ssl.h>
|
#include <event2/bufferevent_ssl.h>
|
||||||
|
|
||||||
|
@ -36,6 +35,7 @@
|
||||||
#include "shrpx_thread_event_receiver.h"
|
#include "shrpx_thread_event_receiver.h"
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
|
#include "shrpx_worker_config.h"
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
#include "shrpx_http2_session.h"
|
#include "shrpx_http2_session.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
@ -51,8 +51,11 @@ ListenHandler::ListenHandler(event_base *evbase, SSL_CTX *sv_ssl_ctx,
|
||||||
cl_ssl_ctx_(cl_ssl_ctx),
|
cl_ssl_ctx_(cl_ssl_ctx),
|
||||||
rate_limit_group_(bufferevent_rate_limit_group_new
|
rate_limit_group_(bufferevent_rate_limit_group_new
|
||||||
(evbase, get_config()->worker_rate_limit_cfg)),
|
(evbase, get_config()->worker_rate_limit_cfg)),
|
||||||
|
evlistener4_(nullptr),
|
||||||
|
evlistener6_(nullptr),
|
||||||
worker_stat_(util::make_unique<WorkerStat>()),
|
worker_stat_(util::make_unique<WorkerStat>()),
|
||||||
worker_round_robin_cnt_(0)
|
worker_round_robin_cnt_(0),
|
||||||
|
num_worker_shutdown_(0)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
ListenHandler::~ListenHandler()
|
ListenHandler::~ListenHandler()
|
||||||
|
@ -68,48 +71,70 @@ void ListenHandler::worker_reopen_log_files()
|
||||||
wev.type = REOPEN_LOG;
|
wev.type = REOPEN_LOG;
|
||||||
|
|
||||||
for(auto& info : workers_) {
|
for(auto& info : workers_) {
|
||||||
bufferevent_write(info.bev, &wev, sizeof(wev));
|
bufferevent_write(info->bev, &wev, sizeof(wev));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void worker_writecb(bufferevent *bev, void *ptr)
|
||||||
|
{
|
||||||
|
auto listener_handler = static_cast<ListenHandler*>(ptr);
|
||||||
|
auto output = bufferevent_get_output(bev);
|
||||||
|
|
||||||
|
if(!worker_config.graceful_shutdown ||
|
||||||
|
evbuffer_get_length(output) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If graceful_shutdown is true and nothing left to send, we sent
|
||||||
|
// graceful shutdown event to worker successfully. The worker is
|
||||||
|
// now doing shutdown.
|
||||||
|
listener_handler->notify_worker_shutdown();
|
||||||
|
|
||||||
|
// Disable bev so that this won' be called accidentally in the
|
||||||
|
// future.
|
||||||
|
bufferevent_disable(bev, EV_READ | EV_WRITE);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void ListenHandler::create_worker_thread(size_t num)
|
void ListenHandler::create_worker_thread(size_t num)
|
||||||
{
|
{
|
||||||
workers_.resize(0);
|
workers_.resize(0);
|
||||||
for(size_t i = 0; i < num; ++i) {
|
for(size_t i = 0; i < num; ++i) {
|
||||||
int rv;
|
int rv;
|
||||||
auto info = WorkerInfo();
|
auto info = util::make_unique<WorkerInfo>();
|
||||||
rv = socketpair(AF_UNIX, SOCK_STREAM, 0, info.sv);
|
rv = socketpair(AF_UNIX, SOCK_STREAM, 0, info->sv);
|
||||||
if(rv == -1) {
|
if(rv == -1) {
|
||||||
LLOG(ERROR, this) << "socketpair() failed: errno=" << errno;
|
LLOG(ERROR, this) << "socketpair() failed: errno=" << errno;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
evutil_make_socket_nonblocking(info.sv[0]);
|
|
||||||
evutil_make_socket_nonblocking(info.sv[1]);
|
for(int j = 0; j < 2; ++j) {
|
||||||
info.sv_ssl_ctx = sv_ssl_ctx_;
|
evutil_make_socket_nonblocking(info->sv[j]);
|
||||||
info.cl_ssl_ctx = cl_ssl_ctx_;
|
evutil_make_socket_closeonexec(info->sv[j]);
|
||||||
try {
|
|
||||||
auto thread = std::thread{start_threaded_worker, info};
|
|
||||||
thread.detach();
|
|
||||||
} catch(const std::system_error& error) {
|
|
||||||
LLOG(ERROR, this) << "Could not start thread: code=" << error.code()
|
|
||||||
<< " msg=" << error.what();
|
|
||||||
for(size_t j = 0; j < 2; ++j) {
|
|
||||||
close(info.sv[j]);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
auto bev = bufferevent_socket_new(evbase_, info.sv[0],
|
|
||||||
|
info->sv_ssl_ctx = sv_ssl_ctx_;
|
||||||
|
info->cl_ssl_ctx = cl_ssl_ctx_;
|
||||||
|
|
||||||
|
info->fut = std::async(std::launch::async, start_threaded_worker,
|
||||||
|
info.get());
|
||||||
|
|
||||||
|
auto bev = bufferevent_socket_new(evbase_, info->sv[0],
|
||||||
BEV_OPT_DEFER_CALLBACKS);
|
BEV_OPT_DEFER_CALLBACKS);
|
||||||
if(!bev) {
|
if(!bev) {
|
||||||
LLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
LLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
||||||
for(size_t j = 0; j < 2; ++j) {
|
for(size_t j = 0; j < 2; ++j) {
|
||||||
close(info.sv[j]);
|
close(info->sv[j]);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
info.bev = bev;
|
|
||||||
|
|
||||||
workers_.push_back(info);
|
bufferevent_setcb(bev, nullptr, worker_writecb, nullptr, this);
|
||||||
|
|
||||||
|
info->bev = bev;
|
||||||
|
|
||||||
|
workers_.push_back(std::move(info));
|
||||||
|
|
||||||
if(LOG_ENABLED(INFO)) {
|
if(LOG_ENABLED(INFO)) {
|
||||||
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
||||||
|
@ -117,12 +142,56 @@ void ListenHandler::create_worker_thread(size_t num)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ListenHandler::join_worker()
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LLOG(INFO, this) << "Waiting for worker thread to join: n="
|
||||||
|
<< workers_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& worker : workers_) {
|
||||||
|
worker->fut.get();
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LLOG(INFO, this) << "Thread #" << n << " joined";
|
||||||
|
}
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenHandler::graceful_shutdown_worker()
|
||||||
|
{
|
||||||
|
if(get_config()->num_worker == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& worker : workers_) {
|
||||||
|
WorkerEvent wev;
|
||||||
|
memset(&wev, 0, sizeof(wev));
|
||||||
|
wev.type = GRACEFUL_SHUTDOWN;
|
||||||
|
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto output = bufferevent_get_output(worker->bev);
|
||||||
|
|
||||||
|
if(evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
||||||
|
LLOG(FATAL, this) << "evbuffer_add() failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int ListenHandler::accept_connection(evutil_socket_t fd,
|
int ListenHandler::accept_connection(evutil_socket_t fd,
|
||||||
sockaddr *addr, int addrlen)
|
sockaddr *addr, int addrlen)
|
||||||
{
|
{
|
||||||
if(LOG_ENABLED(INFO)) {
|
if(LOG_ENABLED(INFO)) {
|
||||||
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evutil_make_socket_closeonexec(fd);
|
||||||
|
|
||||||
if(get_config()->num_worker == 1) {
|
if(get_config()->num_worker == 1) {
|
||||||
|
|
||||||
if(worker_stat_->num_connections >=
|
if(worker_stat_->num_connections >=
|
||||||
|
@ -158,7 +227,7 @@ int ListenHandler::accept_connection(evutil_socket_t fd,
|
||||||
wev.client_fd = fd;
|
wev.client_fd = fd;
|
||||||
memcpy(&wev.client_addr, addr, addrlen);
|
memcpy(&wev.client_addr, addr, addrlen);
|
||||||
wev.client_addrlen = addrlen;
|
wev.client_addrlen = addrlen;
|
||||||
auto output = bufferevent_get_output(workers_[idx].bev);
|
auto output = bufferevent_get_output(workers_[idx]->bev);
|
||||||
if(evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
if(evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
||||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
LLOG(FATAL, this) << "evbuffer_add() failed";
|
||||||
close(fd);
|
close(fd);
|
||||||
|
@ -181,4 +250,92 @@ int ListenHandler::create_http2_session()
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WorkerStat* ListenHandler::get_worker_stat() const
|
||||||
|
{
|
||||||
|
return worker_stat_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenHandler::set_evlistener4(evconnlistener *evlistener4)
|
||||||
|
{
|
||||||
|
evlistener4_ = evlistener4;
|
||||||
|
}
|
||||||
|
|
||||||
|
evconnlistener* ListenHandler::get_evlistener4() const
|
||||||
|
{
|
||||||
|
return evlistener4_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenHandler::set_evlistener6(evconnlistener *evlistener6)
|
||||||
|
{
|
||||||
|
evlistener6_ = evlistener6;
|
||||||
|
}
|
||||||
|
|
||||||
|
evconnlistener* ListenHandler::get_evlistener6() const
|
||||||
|
{
|
||||||
|
return evlistener6_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenHandler::disable_evlistener()
|
||||||
|
{
|
||||||
|
if(evlistener4_) {
|
||||||
|
evconnlistener_disable(evlistener4_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(evlistener6_) {
|
||||||
|
evconnlistener_disable(evlistener6_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void perform_accept_pending_connection(ListenHandler *listener_handler,
|
||||||
|
evconnlistener *listener)
|
||||||
|
{
|
||||||
|
if(!listener) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto server_fd = evconnlistener_get_fd(listener);
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
sockaddr_union sockaddr;
|
||||||
|
socklen_t addrlen = sizeof(sockaddr);
|
||||||
|
|
||||||
|
auto fd = accept(server_fd, &sockaddr.sa, &addrlen);
|
||||||
|
|
||||||
|
if(fd == -1) {
|
||||||
|
if(errno == EINTR ||
|
||||||
|
errno == ENETDOWN ||
|
||||||
|
errno == EPROTO ||
|
||||||
|
errno == ENOPROTOOPT ||
|
||||||
|
errno == EHOSTDOWN ||
|
||||||
|
errno == ENONET ||
|
||||||
|
errno == EHOSTUNREACH ||
|
||||||
|
errno == EOPNOTSUPP ||
|
||||||
|
errno == ENETUNREACH) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evutil_make_socket_nonblocking(fd);
|
||||||
|
|
||||||
|
listener_handler->accept_connection(fd, &sockaddr.sa, addrlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ListenHandler::accept_pending_connection()
|
||||||
|
{
|
||||||
|
perform_accept_pending_connection(this, evlistener4_);
|
||||||
|
perform_accept_pending_connection(this, evlistener6_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenHandler::notify_worker_shutdown()
|
||||||
|
{
|
||||||
|
if(++num_worker_shutdown_ == workers_.size()) {
|
||||||
|
event_base_loopbreak(evbase_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -32,15 +32,18 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <event.h>
|
#include <event.h>
|
||||||
#include <event2/bufferevent.h>
|
#include <event2/bufferevent.h>
|
||||||
|
#include <event2/listener.h>
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
struct WorkerInfo {
|
struct WorkerInfo {
|
||||||
|
std::future<void> fut;
|
||||||
SSL_CTX *sv_ssl_ctx;
|
SSL_CTX *sv_ssl_ctx;
|
||||||
SSL_CTX *cl_ssl_ctx;
|
SSL_CTX *cl_ssl_ctx;
|
||||||
bufferevent *bev;
|
bufferevent *bev;
|
||||||
|
@ -59,8 +62,18 @@ public:
|
||||||
void worker_reopen_log_files();
|
void worker_reopen_log_files();
|
||||||
event_base* get_evbase() const;
|
event_base* get_evbase() const;
|
||||||
int create_http2_session();
|
int create_http2_session();
|
||||||
|
const WorkerStat* get_worker_stat() const;
|
||||||
|
void set_evlistener4(evconnlistener *evlistener4);
|
||||||
|
evconnlistener* get_evlistener4() const;
|
||||||
|
void set_evlistener6(evconnlistener *evlistener6);
|
||||||
|
evconnlistener* get_evlistener6() const;
|
||||||
|
void disable_evlistener();
|
||||||
|
void accept_pending_connection();
|
||||||
|
void graceful_shutdown_worker();
|
||||||
|
void join_worker();
|
||||||
|
void notify_worker_shutdown();
|
||||||
private:
|
private:
|
||||||
std::vector<WorkerInfo> workers_;
|
std::vector<std::unique_ptr<WorkerInfo>> workers_;
|
||||||
event_base *evbase_;
|
event_base *evbase_;
|
||||||
// The frontend server SSL_CTX
|
// The frontend server SSL_CTX
|
||||||
SSL_CTX *sv_ssl_ctx_;
|
SSL_CTX *sv_ssl_ctx_;
|
||||||
|
@ -70,8 +83,11 @@ private:
|
||||||
// multi-threaded case, see shrpx_worker.cc.
|
// multi-threaded case, see shrpx_worker.cc.
|
||||||
std::unique_ptr<Http2Session> http2session_;
|
std::unique_ptr<Http2Session> http2session_;
|
||||||
bufferevent_rate_limit_group *rate_limit_group_;
|
bufferevent_rate_limit_group *rate_limit_group_;
|
||||||
|
evconnlistener *evlistener4_;
|
||||||
|
evconnlistener *evlistener6_;
|
||||||
std::unique_ptr<WorkerStat> worker_stat_;
|
std::unique_ptr<WorkerStat> worker_stat_;
|
||||||
unsigned int worker_round_robin_cnt_;
|
unsigned int worker_round_robin_cnt_;
|
||||||
|
int num_worker_shutdown_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -81,6 +81,22 @@ void ThreadEventReceiver::on_read(bufferevent *bev)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(wev.type == GRACEFUL_SHUTDOWN) {
|
||||||
|
if(LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Graceful shutdown commencing";
|
||||||
|
}
|
||||||
|
|
||||||
|
worker_config.graceful_shutdown = true;
|
||||||
|
|
||||||
|
if(worker_stat_->num_connections == 0) {
|
||||||
|
event_base_loopbreak(evbase_);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(LOG_ENABLED(INFO)) {
|
if(LOG_ENABLED(INFO)) {
|
||||||
TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||||
<< ", addrlen=" << wev.client_addrlen;
|
<< ", addrlen=" << wev.client_addrlen;
|
||||||
|
|
|
@ -42,7 +42,8 @@ struct WorkerStat;
|
||||||
|
|
||||||
enum WorkerEventType {
|
enum WorkerEventType {
|
||||||
NEW_CONNECTION = 0x01,
|
NEW_CONNECTION = 0x01,
|
||||||
REOPEN_LOG = 0x02
|
REOPEN_LOG = 0x02,
|
||||||
|
GRACEFUL_SHUTDOWN = 0x03,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WorkerEvent {
|
struct WorkerEvent {
|
||||||
|
|
|
@ -43,10 +43,10 @@ using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
Worker::Worker(const WorkerInfo& info)
|
Worker::Worker(const WorkerInfo *info)
|
||||||
: sv_ssl_ctx_(info.sv_ssl_ctx),
|
: sv_ssl_ctx_(info->sv_ssl_ctx),
|
||||||
cl_ssl_ctx_(info.cl_ssl_ctx),
|
cl_ssl_ctx_(info->cl_ssl_ctx),
|
||||||
fd_(info.sv[1])
|
fd_(info->sv[1])
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Worker::~Worker()
|
Worker::~Worker()
|
||||||
|
@ -108,7 +108,7 @@ void Worker::run()
|
||||||
event_base_loop(evbase.get(), 0);
|
event_base_loop(evbase.get(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_threaded_worker(WorkerInfo info)
|
void start_threaded_worker(WorkerInfo *info)
|
||||||
{
|
{
|
||||||
Worker worker(info);
|
Worker worker(info);
|
||||||
worker.run();
|
worker.run();
|
||||||
|
|
|
@ -42,7 +42,7 @@ struct WorkerStat {
|
||||||
|
|
||||||
class Worker {
|
class Worker {
|
||||||
public:
|
public:
|
||||||
Worker(const WorkerInfo& info);
|
Worker(const WorkerInfo *info);
|
||||||
~Worker();
|
~Worker();
|
||||||
void run();
|
void run();
|
||||||
private:
|
private:
|
||||||
|
@ -52,7 +52,7 @@ private:
|
||||||
int fd_;
|
int fd_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void start_threaded_worker(WorkerInfo info);
|
void start_threaded_worker(WorkerInfo *info);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ namespace shrpx {
|
||||||
WorkerConfig::WorkerConfig()
|
WorkerConfig::WorkerConfig()
|
||||||
: accesslog_fd(-1),
|
: accesslog_fd(-1),
|
||||||
errorlog_fd(-1),
|
errorlog_fd(-1),
|
||||||
errorlog_tty(false)
|
errorlog_tty(false),
|
||||||
|
graceful_shutdown(false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
|
|
|
@ -34,6 +34,7 @@ struct WorkerConfig {
|
||||||
int errorlog_fd;
|
int errorlog_fd;
|
||||||
// true if errorlog_fd is referring to a terminal.
|
// true if errorlog_fd is referring to a terminal.
|
||||||
bool errorlog_tty;
|
bool errorlog_tty;
|
||||||
|
bool graceful_shutdown;
|
||||||
|
|
||||||
WorkerConfig();
|
WorkerConfig();
|
||||||
};
|
};
|
||||||
|
|
27
src/util.cc
27
src/util.cc
|
@ -589,7 +589,7 @@ bool numeric_host(const char *hostname)
|
||||||
|
|
||||||
int reopen_log_file(const char *path)
|
int reopen_log_file(const char *path)
|
||||||
{
|
{
|
||||||
auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT,
|
auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
|
||||||
S_IRUSR | S_IWUSR | S_IRGRP);
|
S_IRUSR | S_IWUSR | S_IRGRP);
|
||||||
|
|
||||||
if(fd == -1) {
|
if(fd == -1) {
|
||||||
|
@ -616,6 +616,31 @@ std::string ascii_dump(const uint8_t *data, size_t len)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char* get_exec_path(int argc, char **const argv, const char *cwd)
|
||||||
|
{
|
||||||
|
if(argc == 0 || cwd == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto argv0 = argv[0];
|
||||||
|
auto len = strlen(argv0);
|
||||||
|
|
||||||
|
char *path;
|
||||||
|
|
||||||
|
if(argv0[0] == '/') {
|
||||||
|
path = static_cast<char*>(malloc(len + 1));
|
||||||
|
memcpy(path, argv0, len + 1);
|
||||||
|
} else {
|
||||||
|
auto cwdlen = strlen(cwd);
|
||||||
|
path = static_cast<char*>(malloc(len + 1 + cwdlen + 1));
|
||||||
|
memcpy(path, cwd, cwdlen);
|
||||||
|
path[cwdlen] = '/';
|
||||||
|
memcpy(path + cwdlen + 1, argv0, len + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -487,6 +487,14 @@ int reopen_log_file(const char *path);
|
||||||
// characters are preserved. Other characters are replaced with ".".
|
// characters are preserved. Other characters are replaced with ".".
|
||||||
std::string ascii_dump(const uint8_t *data, size_t len);
|
std::string ascii_dump(const uint8_t *data, size_t len);
|
||||||
|
|
||||||
|
// Returns absolute path of executable path. If argc == 0 or |cwd| is
|
||||||
|
// nullptr, this function returns nullptr. If argv[0] starts with
|
||||||
|
// '/', this function returns argv[0]. Oterwise return cwd + "/" +
|
||||||
|
// argv[0]. If non-null is returned, it is NULL-terminated string and
|
||||||
|
// dynamically allocated by malloc. The caller is responsible to free
|
||||||
|
// it.
|
||||||
|
char* get_exec_path(int argc, char **const argv, const char *cwd);
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
Loading…
Reference in New Issue