230d2ae5e6
Before we've jitted the first bit of the TC, having so many threads leads to a pathological situation: we accept a bunch of requests (135) and have all of them running in the interpeter, starving the JIT thread that is trying to make them all go faster. (Perf record of a machine in this situation shows the vast majority of the time in dispatchImpl, SrcDB::find, and enterTC). This diff tries to solve this by limiting the number of threads to the number of cores for the first N requests.
523 linhas
15 KiB
C++
523 linhas
15 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <runtime/base/server/http_server.h>
|
|
#include <runtime/base/server/libevent_server.h>
|
|
#include <runtime/base/server/libevent_server_with_fd.h>
|
|
#include <runtime/base/server/libevent_server_with_takeover.h>
|
|
#include <runtime/base/server/http_request_handler.h>
|
|
#include <runtime/base/server/admin_request_handler.h>
|
|
#include <runtime/base/server/server_stats.h>
|
|
#include <runtime/base/server/xbox_server.h>
|
|
#include <runtime/base/runtime_option.h>
|
|
#include <runtime/base/server/static_content_cache.h>
|
|
#include <runtime/base/class_info.h>
|
|
#include <runtime/base/memory/memory_manager.h>
|
|
#include <util/logger.h>
|
|
#include <runtime/base/externals.h>
|
|
#include <runtime/base/util/http_client.h>
|
|
#include <runtime/base/server/replay_transport.h>
|
|
#include <runtime/base/program_functions.h>
|
|
#include <runtime/eval/debugger/debugger.h>
|
|
#include <util/db_conn.h>
|
|
#include <runtime/ext/ext_apc.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#include <util/ssl_init.h>
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// statics
|
|
|
|
HttpServerPtr HttpServer::Server;
|
|
time_t HttpServer::StartTime;
|
|
|
|
const int kNumProcessors = sysconf(_SC_NPROCESSORS_ONLN);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HttpServer::HttpServer(void *sslCTX /* = NULL */)
|
|
: m_stopped(false), m_sslCTX(sslCTX),
|
|
m_watchDog(this, &HttpServer::watchDog) {
|
|
|
|
// enabling mutex profiling, but it's not turned on
|
|
LockProfiler::s_pfunc_profile = server_stats_log_mutex;
|
|
|
|
bool const useWarmupThrottle =
|
|
RuntimeOption::ServerWarmupThrottleRequestCount > 0 &&
|
|
RuntimeOption::ServerThreadCount > kNumProcessors;
|
|
|
|
auto maybeEnableThrottle = [&] (LibEventServer* s) {
|
|
if (!useWarmupThrottle) return;
|
|
|
|
s->enableWarmupThrottle(RuntimeOption::ServerThreadCount - kNumProcessors,
|
|
RuntimeOption::ServerWarmupThrottleRequestCount);
|
|
Logger::Info("Starting with %d threads for the first %d requests\n",
|
|
kNumProcessors,
|
|
RuntimeOption::ServerWarmupThrottleRequestCount);
|
|
};
|
|
|
|
int const startingThreadCount = !useWarmupThrottle
|
|
? RuntimeOption::ServerThreadCount
|
|
: kNumProcessors;
|
|
|
|
if (RuntimeOption::ServerPortFd != -1 || RuntimeOption::SSLPortFd != -1) {
|
|
LibEventServerWithFd* server =
|
|
(new TypedServer<LibEventServerWithFd, HttpRequestHandler>
|
|
(RuntimeOption::ServerIP, RuntimeOption::ServerPort,
|
|
startingThreadCount,
|
|
RuntimeOption::RequestTimeoutSeconds));
|
|
maybeEnableThrottle(server);
|
|
server->setServerSocketFd(RuntimeOption::ServerPortFd);
|
|
server->setSSLSocketFd(RuntimeOption::SSLPortFd);
|
|
m_pageServer = ServerPtr(server);
|
|
} else if (RuntimeOption::TakeoverFilename.empty()) {
|
|
auto const server = new TypedServer<LibEventServer, HttpRequestHandler>
|
|
(RuntimeOption::ServerIP, RuntimeOption::ServerPort,
|
|
startingThreadCount,
|
|
RuntimeOption::RequestTimeoutSeconds);
|
|
maybeEnableThrottle(server);
|
|
m_pageServer = ServerPtr(server);
|
|
} else {
|
|
LibEventServerWithTakeover* server =
|
|
(new TypedServer<LibEventServerWithTakeover, HttpRequestHandler>
|
|
(RuntimeOption::ServerIP, RuntimeOption::ServerPort,
|
|
startingThreadCount,
|
|
RuntimeOption::RequestTimeoutSeconds));
|
|
maybeEnableThrottle(server);
|
|
server->setTransferFilename(RuntimeOption::TakeoverFilename);
|
|
server->addTakeoverListener(this);
|
|
m_pageServer = ServerPtr(server);
|
|
}
|
|
|
|
if (RuntimeOption::EnableSSL && m_sslCTX) {
|
|
assert(SSLInit::IsInited());
|
|
m_pageServer->enableSSL(m_sslCTX, RuntimeOption::SSLPort);
|
|
}
|
|
|
|
m_adminServer = ServerPtr
|
|
(new TypedServer<LibEventServer, AdminRequestHandler>
|
|
(RuntimeOption::ServerIP, RuntimeOption::AdminServerPort,
|
|
RuntimeOption::AdminThreadCount,
|
|
RuntimeOption::RequestTimeoutSeconds));
|
|
|
|
for (unsigned int i = 0; i < RuntimeOption::SatelliteServerInfos.size();
|
|
i++) {
|
|
SatelliteServerInfoPtr info = RuntimeOption::SatelliteServerInfos[i];
|
|
SatelliteServerPtr satellite = SatelliteServer::Create(info);
|
|
if (satellite) {
|
|
if (info->getType() == SatelliteServer::KindOfDanglingPageServer) {
|
|
m_danglings.push_back(satellite);
|
|
} else {
|
|
m_satellites.push_back(satellite);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (RuntimeOption::XboxServerPort != 0) {
|
|
SatelliteServerInfoPtr xboxInfo(new XboxServerInfo());
|
|
SatelliteServerPtr satellite = SatelliteServer::Create(xboxInfo);
|
|
if (satellite) {
|
|
m_satellites.push_back(satellite);
|
|
}
|
|
}
|
|
|
|
if (RuntimeOption::EnableStaticContentCache) {
|
|
StaticContentCache::TheCache.load();
|
|
}
|
|
|
|
hphp_process_init();
|
|
|
|
Server::InstallStopSignalHandlers(m_pageServer);
|
|
Server::InstallStopSignalHandlers(m_adminServer);
|
|
|
|
if (!RuntimeOption::StartupDocument.empty()) {
|
|
Hdf hdf;
|
|
hdf["cmd"] = Transport::GET;
|
|
hdf["url"] = RuntimeOption::StartupDocument;
|
|
hdf["remote_host"] = RuntimeOption::ServerIP;
|
|
|
|
ReplayTransport rt;
|
|
rt.replayInput(hdf);
|
|
HttpRequestHandler handler;
|
|
handler.handleRequest(&rt);
|
|
int code = rt.getResponseCode();
|
|
if (code == 200) {
|
|
Logger::Info("StartupDocument %s returned 200 OK: %s",
|
|
RuntimeOption::StartupDocument.c_str(),
|
|
rt.getResponse().c_str());
|
|
} else {
|
|
Logger::Error("StartupDocument %s failed %d: %s",
|
|
RuntimeOption::StartupDocument.c_str(),
|
|
code, rt.getResponse().data());
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < RuntimeOption::ThreadDocuments.size(); i++) {
|
|
ServiceThreadPtr thread
|
|
(new ServiceThread(RuntimeOption::ThreadDocuments[i]));
|
|
m_serviceThreads.push_back(thread);
|
|
}
|
|
|
|
for (unsigned int i = 0; i < RuntimeOption::ThreadLoopDocuments.size(); i++) {
|
|
ServiceThreadPtr thread
|
|
(new ServiceThread(RuntimeOption::ThreadLoopDocuments[i], true));
|
|
m_serviceThreads.push_back(thread);
|
|
}
|
|
}
|
|
|
|
void HttpServer::onServerShutdown() {
|
|
Eval::Debugger::Stop();
|
|
if (RuntimeOption::EnableDebuggerServer) {
|
|
Logger::Info("debugger server stopped");
|
|
}
|
|
|
|
XboxServer::Stop();
|
|
|
|
// When a new instance of HPHP has taken over our page server socket,
|
|
// stop our admin server and satellites so it can acquire those ports.
|
|
for (unsigned int i = 0; i < m_satellites.size(); i++) {
|
|
string name = m_satellites[i]->getName();
|
|
m_satellites[i]->stop();
|
|
Logger::Info("satellite server %s stopped", name.c_str());
|
|
}
|
|
if (RuntimeOption::AdminServerPort) {
|
|
m_adminServer->stop();
|
|
Logger::Info("admin server stopped");
|
|
}
|
|
|
|
// start dangling servers, so they can serve old version of pages
|
|
for (unsigned int i = 0; i < m_danglings.size(); i++) {
|
|
string name = m_danglings[i]->getName();
|
|
try {
|
|
m_danglings[i]->start();
|
|
Logger::Info("dangling server %s started", name.c_str());
|
|
} catch (Exception &e) {
|
|
Logger::Error("Unable to start danglings server %s: %s",
|
|
name.c_str(), e.getMessage().c_str());
|
|
// it's okay not able to start them
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpServer::takeoverShutdown(LibEventServerWithTakeover* server) {
|
|
assert(server == m_pageServer.get());
|
|
// We want to synchronously shut down our satellite servers to free up ports,
|
|
// then asynchronously shut down everything else.
|
|
onServerShutdown();
|
|
stop();
|
|
}
|
|
|
|
HttpServer::~HttpServer() {
|
|
stop();
|
|
}
|
|
|
|
void HttpServer::run() {
|
|
StartTime = time(0);
|
|
|
|
m_watchDog.start();
|
|
|
|
for (unsigned int i = 0; i < m_serviceThreads.size(); i++) {
|
|
m_serviceThreads[i]->start();
|
|
}
|
|
for (unsigned int i = 0; i < m_serviceThreads.size(); i++) {
|
|
m_serviceThreads[i]->waitForStarted();
|
|
}
|
|
|
|
if (RuntimeOption::ServerPort) {
|
|
if (!startServer(true)) {
|
|
Logger::Error("Unable to start page server");
|
|
return;
|
|
}
|
|
Logger::Info("page server started");
|
|
}
|
|
|
|
if (RuntimeOption::AdminServerPort) {
|
|
if (!startServer(false)) {
|
|
Logger::Error("Unable to start admin server");
|
|
abortServers();
|
|
return;
|
|
}
|
|
Logger::Info("admin server started");
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_satellites.size(); i++) {
|
|
string name = m_satellites[i]->getName();
|
|
try {
|
|
m_satellites[i]->start();
|
|
Logger::Info("satellite server %s started", name.c_str());
|
|
} catch (Exception &e) {
|
|
Logger::Error("Unable to start satellite server %s: %s",
|
|
name.c_str(), e.getMessage().c_str());
|
|
abortServers();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!Eval::Debugger::StartServer()) {
|
|
Logger::Error("Unable to start debugger server");
|
|
abortServers();
|
|
return;
|
|
} else if (RuntimeOption::EnableDebuggerServer) {
|
|
Logger::Info("debugger server started");
|
|
}
|
|
|
|
{
|
|
Logger::Info("all servers started");
|
|
createPid();
|
|
Lock lock(this);
|
|
// continously running until /stop is received on admin server
|
|
while (!m_stopped) {
|
|
wait();
|
|
}
|
|
removePid();
|
|
Logger::Info("page server stopped");
|
|
}
|
|
|
|
onServerShutdown(); // dangling server already started here
|
|
time_t t0 = time(0);
|
|
if (RuntimeOption::ServerPort) {
|
|
m_pageServer->stop();
|
|
}
|
|
time_t t1 = time(0);
|
|
if (!m_danglings.empty() && RuntimeOption::ServerDanglingWait > 0) {
|
|
int elapsed = t1 - t0;
|
|
if (RuntimeOption::ServerDanglingWait > elapsed) {
|
|
sleep(RuntimeOption::ServerDanglingWait - elapsed);
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_danglings.size(); i++) {
|
|
m_danglings[i]->stop();
|
|
Logger::Info("dangling server %s stopped",
|
|
m_danglings[i]->getName().c_str());
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_serviceThreads.size(); i++) {
|
|
m_serviceThreads[i]->notifyStopped();
|
|
}
|
|
for (unsigned int i = 0; i < m_serviceThreads.size(); i++) {
|
|
m_serviceThreads[i]->waitForEnd();
|
|
}
|
|
|
|
hphp_process_exit();
|
|
m_watchDog.waitForEnd();
|
|
Logger::Info("all servers stopped");
|
|
}
|
|
|
|
static void exit_on_timeout(int sig) {
|
|
signal(sig, SIG_DFL);
|
|
kill(getpid(), SIGKILL);
|
|
exit(0);
|
|
}
|
|
|
|
void HttpServer::stop() {
|
|
if (RuntimeOption::ServerGracefulShutdownWait) {
|
|
signal(SIGALRM, exit_on_timeout);
|
|
alarm(RuntimeOption::ServerGracefulShutdownWait);
|
|
}
|
|
|
|
Lock lock(this);
|
|
m_stopped = true;
|
|
notify();
|
|
}
|
|
|
|
void HttpServer::abortServers() {
|
|
for (unsigned int i = 0; i < m_satellites.size(); i++) {
|
|
m_satellites[i]->stop();
|
|
}
|
|
if (RuntimeOption::AdminServerPort) {
|
|
m_adminServer->stop();
|
|
}
|
|
if (RuntimeOption::ServerPort) {
|
|
m_pageServer->stop();
|
|
}
|
|
}
|
|
|
|
void HttpServer::createPid() {
|
|
if (!RuntimeOption::PidFile.empty()) {
|
|
FILE * f = fopen(RuntimeOption::PidFile.c_str(), "w");
|
|
if (f) {
|
|
pid_t pid = Process::GetProcessId();
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "%" PRId64, (int64_t)pid);
|
|
fwrite(buf, strlen(buf), 1, f);
|
|
fclose(f);
|
|
} else {
|
|
Logger::Error("Unable to open pid file %s for write",
|
|
RuntimeOption::PidFile.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpServer::removePid() {
|
|
if (!RuntimeOption::PidFile.empty()) {
|
|
unlink(RuntimeOption::PidFile.c_str());
|
|
}
|
|
}
|
|
|
|
void HttpServer::killPid() {
|
|
if (!RuntimeOption::PidFile.empty()) {
|
|
CstrBuffer sb(RuntimeOption::PidFile.c_str());
|
|
if (sb.size()) {
|
|
int64_t pid = sb.detach().toInt64();
|
|
if (pid) {
|
|
kill((pid_t)pid, SIGKILL);
|
|
return;
|
|
}
|
|
}
|
|
Logger::Error("Unable to read pid file %s for any meaningful pid",
|
|
RuntimeOption::PidFile.c_str());
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// watch dog thread
|
|
|
|
void HttpServer::watchDog() {
|
|
int count = 0;
|
|
bool noneed = false;
|
|
while (!m_stopped && !noneed) {
|
|
noneed = true;
|
|
|
|
if (RuntimeOption::DropCacheCycle > 0) {
|
|
noneed = false;
|
|
if ((count % RuntimeOption::DropCacheCycle) == 0) { // every hour
|
|
dropCache();
|
|
}
|
|
}
|
|
|
|
sleep(1);
|
|
++count;
|
|
|
|
if (RuntimeOption::MaxRSSPollingCycle > 0) {
|
|
noneed = false;
|
|
if ((count % RuntimeOption::MaxRSSPollingCycle) == 0) { // every minute
|
|
checkMemory();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpServer::dropCache() {
|
|
FILE *f = fopen("/proc/sys/vm/drop_caches", "w");
|
|
if (f) {
|
|
// http://www.linuxinsight.com/proc_sys_vm_drop_caches.html
|
|
const char *FREE_ALL_CACHES = "3\n";
|
|
fwrite(FREE_ALL_CACHES, 2, 1, f);
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
void HttpServer::checkMemory() {
|
|
if (RuntimeOption::MaxRSS > 0 &&
|
|
Process::GetProcessRSS(Process::GetProcessId()) * 1024 * 1024 >
|
|
RuntimeOption::MaxRSS) {
|
|
stop();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// page server
|
|
|
|
bool HttpServer::startServer(bool pageServer) {
|
|
int port = pageServer ?
|
|
RuntimeOption::ServerPort : RuntimeOption::AdminServerPort;
|
|
|
|
// 1. try something nice
|
|
for (unsigned int i = 0; i < 60; i++) {
|
|
try {
|
|
if (pageServer) {
|
|
m_pageServer->start();
|
|
} else {
|
|
m_adminServer->start();
|
|
}
|
|
return true;
|
|
} catch (FailedToListenException &e) {
|
|
if (i == 0) {
|
|
Logger::Info("shutting down old HPHP server by /stop command");
|
|
}
|
|
|
|
if (errno == EACCES) {
|
|
Logger::Error("Permission denied listening on port %d", port);
|
|
return false;
|
|
}
|
|
|
|
HttpClient http;
|
|
string url = "http://";
|
|
url += RuntimeOption::ServerIP;
|
|
url += ":";
|
|
url += lexical_cast<string>(RuntimeOption::AdminServerPort);
|
|
url += "/stop";
|
|
StringBuffer response;
|
|
http.get(url.c_str(), response);
|
|
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
// 2. try something harsh
|
|
if (RuntimeOption::ServerHarshShutdown) {
|
|
for (unsigned int i = 0; i < 5; i++) {
|
|
try {
|
|
if (pageServer) {
|
|
m_pageServer->start();
|
|
} else {
|
|
m_adminServer->start();
|
|
}
|
|
return true;
|
|
} catch (FailedToListenException &e) {
|
|
if (i == 0) {
|
|
Logger::Info("shutting down old HPHP server by pid file");
|
|
}
|
|
killPid();
|
|
sleep(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. try something evil
|
|
if (RuntimeOption::ServerEvilShutdown) {
|
|
for (unsigned int i = 0; i < 60; i++) {
|
|
try {
|
|
if (pageServer) {
|
|
m_pageServer->start();
|
|
} else {
|
|
m_adminServer->start();
|
|
}
|
|
return true;
|
|
} catch (FailedToListenException &e) {
|
|
if (i == 0) {
|
|
Logger::Info("killing anything listening on port %d", port);
|
|
}
|
|
|
|
string cmd = "lsof -t -i :";
|
|
cmd += lexical_cast<string>(port);
|
|
cmd += " | xargs kill -9";
|
|
Util::ssystem(cmd.c_str());
|
|
|
|
sleep(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|