diff --git a/ext/apache2/Configuration.cpp b/ext/apache2/Configuration.cpp index 0a09bed..97b36dc 100644 --- a/ext/apache2/Configuration.cpp +++ b/ext/apache2/Configuration.cpp @@ -27,6 +27,13 @@ #include "Configuration.h" #include "Utils.h" +#include "Logging.h" + +#ifndef _SYS_RESOURCE_H +# include +# include +#endif + /* The APR headers must come after the Passenger headers. See Hooks.cpp * to learn why. */ @@ -41,6 +48,18 @@ extern "C" module AP_MODULE_DECLARE_DATA passenger_module; #define DEFAULT_POOL_IDLE_TIME 300 #define DEFAULT_MAX_INSTANCES_PER_APP 0 +#ifdef RLIM_INFINITY +# define DEFAULT_MEMLIMIT RLIM_INFINITY +#else +# define DEFAULT_MEMLIMIT (~0UL) +#endif + +#define ASSIGN_RLIMIT(type, var) do {\ + struct rlimit rl_##type = { DEFAULT_MEMLIMIT, DEFAULT_MEMLIMIT }; \ + getrlimit(RLIMIT_##type, &rl_##type); \ + var = (__typeof__((var))) rl_##type.rlim_max; \ + } while(0); + template static apr_status_t destroy_config_struct(void *x) { @@ -148,6 +167,12 @@ passenger_config_create_server(apr_pool_t *p, server_rec *s) { config->userSwitching = true; config->userSwitchingSpecified = false; config->defaultUser = NULL; + config->memLimitSpecified = false; + ASSIGN_RLIMIT(STACK, config->stackLimit); + ASSIGN_RLIMIT(DATA, config->dataLimit); + ASSIGN_RLIMIT(AS, config->vmLimit); + ASSIGN_RLIMIT(RSS, config->rssLimit); + config->memLimit = config->vmLimit; config->tempDir = NULL; return config; } @@ -170,7 +195,12 @@ passenger_config_merge_server(apr_pool_t *p, void *basev, void *addv) { config->userSwitching = (add->userSwitchingSpecified) ? add->userSwitching : base->userSwitching; config->userSwitchingSpecified = base->userSwitchingSpecified || add->userSwitchingSpecified; config->defaultUser = (add->defaultUser == NULL) ? base->defaultUser : add->defaultUser; - config->tempDir = (add->tempDir == NULL) ? base->tempDir : add->tempDir; + config->stackLimit = add->memLimitSpecified ? add->stackLimit : base->stackLimit; + config->dataLimit = add->memLimitSpecified ? add->dataLimit : base->dataLimit; + config->vmLimit = add->memLimitSpecified ? add->vmLimit : base->vmLimit; + config->rssLimit = add->memLimitSpecified ? add->rssLimit : base->rssLimit; + config->memLimit = add->memLimitSpecified ? add->memLimit : base->memLimit; + config->memLimitSpecified = base->memLimitSpecified || add->memLimitSpecified; config->tempDir = (add->tempDir == NULL) ? base->tempDir : add->tempDir; return config; } @@ -193,6 +223,12 @@ passenger_config_merge_all_servers(apr_pool_t *pool, server_rec *main_server) { final->userSwitching = (config->userSwitchingSpecified) ? config->userSwitching : final->userSwitching; final->userSwitchingSpecified = final->userSwitchingSpecified || config->userSwitchingSpecified; final->defaultUser = (final->defaultUser != NULL) ? final->defaultUser : config->defaultUser; + final->stackLimit = final->memLimitSpecified ? final->stackLimit : config->stackLimit; + final->dataLimit = final->memLimitSpecified ? final->dataLimit : config->dataLimit; + final->vmLimit = final->memLimitSpecified ? final->vmLimit : config->vmLimit; + final->rssLimit = final->memLimitSpecified ? final->rssLimit : config->rssLimit; + final->memLimit = final->memLimitSpecified ? final->memLimit : config->memLimit; + final->memLimitSpecified = final->memLimitSpecified || config->memLimitSpecified; final->tempDir = (final->tempDir != NULL) ? final->tempDir : config->tempDir; } for (s = main_server; s != NULL; s = s->next) { @@ -296,6 +332,51 @@ cmd_passenger_pool_idle_time(cmd_parms *cmd, void *pcfg, const char *arg) { } static const char * +cmd_passenger_rlimit_memory(cmd_parms *cmd, void *dummy, const char *arg) { + ServerConfig *sconfig = (ServerConfig *) ap_get_module_config( + cmd->server->module_config, &passenger_module); + +#ifdef strtoul +# undef strtoul +#endif + + char *endp; + unsigned int shift = 0; + unsigned long limit = (unsigned long) strtoul(arg, &endp, 10); + + if ( endp != NULL && *endp != '\0' ) { + switch (*endp & 0x4F) { + case 0x4B: /* K */ + shift = 10; + break; + case 0x4D: /* M */ + shift = 20; + break; + default: ; + } + } + + limit <<= shift; + page_align(limit); + + if (0 < limit) { + sconfig->stackLimit = limit; + sconfig->dataLimit = limit; + sconfig->vmLimit = limit; + sconfig->rssLimit = limit; + sconfig->memLimit = limit; + } else { + ASSIGN_RLIMIT(STACK, sconfig->stackLimit); + ASSIGN_RLIMIT(DATA, sconfig->dataLimit); + ASSIGN_RLIMIT(AS, sconfig->vmLimit); + ASSIGN_RLIMIT(RSS, sconfig->rssLimit); + sconfig->memLimit = sconfig->vmLimit; + } + sconfig->memLimitSpecified = true; + return NULL; +} + +static const char * cmd_passenger_use_global_queue(cmd_parms *cmd, void *pcfg, int arg) { DirConfig *config = (DirConfig *) pcfg; if (arg) { @@ -591,7 +672,12 @@ const command_rec passenger_commands[] = { NULL, RSRC_CONF, "Whether to enable user switching support."), - AP_INIT_TAKE1("PassengerDefaultUser", + AP_INIT_TAKE1("PassengerMemLimit", + (Take1Func) cmd_passenger_rlimit_memory, + NULL, + RSRC_CONF, + "Maximum available memory (stack, data, vm, rss) pro running rails instance"), + AP_INIT_TAKE1("PassengerDefaultUser", (Take1Func) cmd_passenger_default_user, NULL, RSRC_CONF, diff --git a/ext/apache2/Configuration.h b/ext/apache2/Configuration.h index 6325fd4..714a325 100644 --- a/ext/apache2/Configuration.h +++ b/ext/apache2/Configuration.h @@ -41,6 +41,10 @@ #include #include + +#include +#include + /** * @defgroup Configuration Apache module configuration * @ingroup Core @@ -311,6 +315,23 @@ * this server config. */ bool userSwitchingSpecified; + /** limit the stack segment */ + unsigned long stackLimit; + + /** limit the data segment */ + unsigned long dataLimit; + + /** limit the process' virtual address space */ + unsigned long vmLimit ; + + /** limit the process' resident-set size */ + unsigned long rssLimit; + + + unsigned long memLimit; + + bool memLimitSpecified; + /** The user that applications must run as if user switching * fails or is disabled. NULL means the option is not specified. */ diff --git a/ext/apache2/Hooks.cpp b/ext/apache2/Hooks.cpp index 46eea85..4487a61 100644 --- a/ext/apache2/Hooks.cpp +++ b/ext/apache2/Hooks.cpp @@ -1077,6 +1077,7 @@ public: const char *ruby, *user; string applicationPoolServerExe, spawnServer; + unsigned long memoryLimit; /* * As described in the comment in init_module, upon (re)starting @@ -1121,16 +1122,18 @@ public: throw FileNotFoundException(message); } + memoryLimit = config->memLimitSpecified ? config->memLimit : 0UL; applicationPoolServer = ptr( new ApplicationPoolServer( applicationPoolServerExe, spawnServer, "", - ruby, user) + ruby, user, memoryLimit) ); ApplicationPoolPtr pool(applicationPoolServer->connect()); pool->setMax(config->maxPoolSize); pool->setMaxPerApp(config->maxInstancesPerApp); pool->setMaxIdleTime(config->poolIdleTime); + pool->setMemLimit(config->memLimit); } ~Hooks() { diff --git a/ext/common/Application.h b/ext/common/Application.h index 6329e9a..02a01ba 100644 --- a/ext/common/Application.h +++ b/ext/common/Application.h @@ -331,6 +331,7 @@ private: string listenSocketName; string listenSocketType; int ownerPipe; + unsigned long memLimit; SessionPtr connectToUnixServer(const function &closeCallback) const { TRACE_POINT(); @@ -401,12 +402,13 @@ public: * @post getAppRoot() == theAppRoot && getPid() == pid */ Application(const string &theAppRoot, pid_t pid, const string &listenSocketName, - const string &listenSocketType, int ownerPipe) { + const string &listenSocketType, int ownerPipe, unsigned long memLimit = 83886080) { appRoot = theAppRoot; this->pid = pid; this->listenSocketName = listenSocketName; this->listenSocketType = listenSocketType; this->ownerPipe = ownerPipe; + this->memLimit = memLimit; P_TRACE(3, "Application " << this << ": created."); } diff --git a/ext/common/ApplicationPool.h b/ext/common/ApplicationPool.h index e2006a1..a96e317 100644 --- a/ext/common/ApplicationPool.h +++ b/ext/common/ApplicationPool.h @@ -189,6 +189,11 @@ public: */ virtual void setMaxPerApp(unsigned int max) = 0; + /** + * Set upper limit for memory usage + */ + virtual void setMemLimit(unsigned long max) = 0; + /** * Get the process ID of the spawn server that is used. * diff --git a/ext/common/ApplicationPoolServer.h b/ext/common/ApplicationPoolServer.h index 9c2f4ea..4d1cc17 100644 --- a/ext/common/ApplicationPoolServer.h +++ b/ext/common/ApplicationPoolServer.h @@ -359,7 +359,18 @@ private: throw; } } - + + virtual void setMemLimit(unsigned long max) { + MessageChannel channel(data->server); + boost::mutex::scoped_lock l(data->lock); + try { + channel.write("setMemLimit", toString(max).c_str(), NULL); + } catch (...) { + data->disconnect(); + throw; + } + } + virtual pid_t getSpawnServerPid() const { this_thread::disable_syscall_interruption dsi; MessageChannel channel(data->server); @@ -511,6 +522,7 @@ private: string m_logFile; string m_rubyCommand; string m_user; + unsigned long m_memLimit; /** * The PID of the ApplicationPool server process. If no server process @@ -661,6 +673,32 @@ private: } } + void limitHotMemoryResource() { + + int e = 0; + struct rlimit *rl = (struct rlimit *) malloc(sizeof(struct rlimit)); + + if (rl == NULL) { + e = errno; + P_WARN("*** WARNING: Could not allocate memory to query rlimit: " << + strerror(e) << " (" << e << ") " << endl); + } + + if (0UL < m_memLimit) { // change only unless default given (-1) + page_align(m_memLimit); + rl->rlim_cur = rl->rlim_max = (rlim_t)m_memLimit; + setrlimit(RLIMIT_STACK, rl); + setrlimit(RLIMIT_DATA, rl); + setrlimit(RLIMIT_AS, rl); + rl->rlim_cur >>= 12; + rl->rlim_max >>= 12; + setrlimit(RLIMIT_RSS, rl); + } + + if (rl != NULL) + free(rl); + } + public: /** * Create a new ApplicationPoolServer object. @@ -688,16 +726,19 @@ public: const string &spawnServerCommand, const string &logFile = "", const string &rubyCommand = "ruby", - const string &user = "") + const string &user = "", + unsigned long memoryLimit = 0UL) : m_serverExecutable(serverExecutable), m_spawnServerCommand(spawnServerCommand), m_logFile(logFile), m_rubyCommand(rubyCommand), - m_user(user) { + m_user(user), + m_memLimit(memoryLimit) { TRACE_POINT(); serverSocket = -1; serverPid = 0; this_thread::disable_syscall_interruption dsi; + limitHotMemoryResource(); restartServer(); } diff --git a/ext/common/ApplicationPoolServerExecutable.cpp b/ext/common/ApplicationPoolServerExecutable.cpp index 147ea4e..37e858b 100644 --- a/ext/common/ApplicationPoolServerExecutable.cpp +++ b/ext/common/ApplicationPoolServerExecutable.cpp @@ -506,6 +506,11 @@ private: server.pool->setMaxPerApp(maxPerApp); } + void processSetMemLimit(unsigned long maxLimit) { + TRACE_POINT(); + server.pool->setMemLimit(maxLimit); + } + void processGetSpawnServerPid(const vector &args) { TRACE_POINT(); channel.write(toString(server.pool->getSpawnServerPid()).c_str(), NULL); @@ -563,6 +568,8 @@ private: processGetCount(args); } else if (args[0] == "setMaxPerApp" && args.size() == 2) { processSetMaxPerApp(atoi(args[1])); + } else if (args[0] == "setMemLimit" && args.size() == 2) { + processSetMemLimit(strtoul(args[1].c_str(), NULL, 10)); } else if (args[0] == "getSpawnServerPid" && args.size() == 1) { processGetSpawnServerPid(args); } else { diff --git a/ext/common/StandardApplicationPool.h b/ext/common/StandardApplicationPool.h index 8572dcc..7f089cf 100644 --- a/ext/common/StandardApplicationPool.h +++ b/ext/common/StandardApplicationPool.h @@ -246,6 +246,7 @@ private: bool detached; bool done; unsigned int maxIdleTime; + unsigned long maxMemory; unsigned int waitingOnGlobalQueue; condition cleanerThreadSleeper; CachedFileStat cstat; @@ -316,6 +317,14 @@ private: result << "count = " << count << endl; result << "active = " << active << endl; result << "inactive = " << inactiveApps.size() << endl; + result << "max memory = "; + if ( maxMemory >> 20 > 0 ) + result << ( maxMemory >> 20 ) << "M" ; + else if ( maxMemory >> 10 > 0 ) + result << ( maxMemory >> 10 ) << "K" ; + else + result << maxMemory ; + result << endl; result << "Waiting on global queue: " << waitingOnGlobalQueue << endl; result << endl; @@ -751,6 +760,13 @@ public: this->maxPerApp = maxPerApp; activeOrMaxChanged.notify_all(); } + + virtual void setMemLimit(unsigned long max) { + boost::mutex::scoped_lock l(lock); + this->maxMemory = max; + activeOrMaxChanged.notify_all(); + } + virtual pid_t getSpawnServerPid() const { return spawnManager.getServerPid(); diff --git a/ext/common/Utils.h b/ext/common/Utils.h index e714e17..f4f5eaf 100644 --- a/ext/common/Utils.h +++ b/ext/common/Utils.h @@ -47,6 +47,12 @@ using namespace boost; typedef struct CachedFileStat CachedFileStat; +static __attribute__((always_inline, regparm(1))) void +page_align(unsigned long& address) { + register unsigned long psz = (unsigned long) getpagesize(); + address = psz + ((address - 1) & ~(psz - 1)); +} + /** Enumeration which indicates what kind of file a file is. */ typedef enum { /** The file doesn't exist. */