0
#define _PASSENGER_APPLICATION_POOL_H_
0
#include <boost/shared_ptr.hpp>
0
-#include <boost/weak_ptr.hpp>
0
-#include <boost/thread.hpp>
0
-#include <boost/thread/mutex.hpp>
0
-#include <boost/thread/condition.hpp>
0
-#include <boost/bind.hpp>
0
-#ifdef TESTING_APPLICATION_POOL
0
-#ifdef PASSENGER_USE_DUMMY_SPAWN_MANAGER
0
- #include "DummySpawnManager.h"
0
- #include "SpawnManager.h"
0
+#include "Application.h"
0
@@ -166,476 +145,6 @@ public:
0
virtual pid_t getSpawnServerPid() const = 0;
0
-class ApplicationPoolServer;
0
-/****************************************************************
0
- * See "doc/ApplicationPool algorithm.txt" for a more readable
0
- * and detailed description of the algorithm implemented here.
0
- ****************************************************************/
0
- * A standard implementation of ApplicationPool for single-process environments.
0
- * The environment may or may not be multithreaded - StandardApplicationPool is completely
0
- * thread-safe. Apache with the threading MPM is an example of a multithreaded single-process
0
- * This class is unusable in multi-process environments such as Apache with the prefork MPM.
0
- * The reasons are as follows:
0
- * - StandardApplicationPool uses threads internally. Because threads disappear after a fork(),
0
- * a StandardApplicationPool object will become unusable after a fork().
0
- * - StandardApplicationPool stores its internal cache on the heap. Different processes
0
- * cannot share their heaps, so they will not be able to access each others' pool cache.
0
- * - StandardApplicationPool has a connection to the spawn server. If there are multiple
0
- * processes, and they all use the spawn servers's connection at the same time without
0
- * some sort of synchronization, then bad things will happen.
0
- * (Of course, StandardApplicationPool <em>is</em> usable if each process creates its own
0
- * StandardApplicationPool object, but that would defeat the point of having a shared pool.)
0
- * For multi-process environments, one should use ApplicationPoolServer instead.
0
-class StandardApplicationPool: public ApplicationPool {
0
- static const int DEFAULT_MAX_IDLE_TIME = 120;
0
- static const int DEFAULT_MAX_POOL_SIZE = 20;
0
- friend class ApplicationPoolServer;
0
- typedef shared_ptr<AppContainer> AppContainerPtr;
0
- typedef list<AppContainerPtr> AppContainerList;
0
- typedef shared_ptr<AppContainerList> AppContainerListPtr;
0
- typedef map<string, AppContainerListPtr> ApplicationMap;
0
- unsigned int sessions;
0
- AppContainerList::iterator iterator;
0
- AppContainerList::iterator ia_iterator;
0
- condition activeOrMaxChanged;
0
- AppContainerList inactiveApps;
0
- map<string, time_t> restartFileTimes;
0
- typedef shared_ptr<SharedData> SharedDataPtr;
0
- struct SessionCloseCallback {
0
- weak_ptr<AppContainer> container;
0
- SessionCloseCallback(SharedDataPtr data,
0
- const weak_ptr<AppContainer> &container) {
0
- this->container = container;
0
- mutex::scoped_lock l(data->lock);
0
- AppContainerPtr container(this->container.lock());
0
- if (container == NULL) {
0
- ApplicationMap::iterator it;
0
- it = data->apps.find(container->app->getAppRoot());
0
- if (it != data->apps.end()) {
0
- AppContainerListPtr list(it->second);
0
- container->lastUsed = time(NULL);
0
- container->sessions--;
0
- if (container->sessions == 0) {
0
- list->erase(container->iterator);
0
- list->push_front(container);
0
- container->iterator = list->begin();
0
- data->inactiveApps.push_back(container);
0
- container->ia_iterator = data->inactiveApps.end();
0
- container->ia_iterator--;
0
- data->activeOrMaxChanged.notify_all();
0
- #ifdef PASSENGER_USE_DUMMY_SPAWN_MANAGER
0
- DummySpawnManager spawnManager;
0
- SpawnManager spawnManager;
0
- thread *cleanerThread;
0
- unsigned int maxIdleTime;
0
- condition cleanerThreadSleeper;
0
- // Shortcuts for instance variables in SharedData. Saves typing in get().
0
- condition &activeOrMaxChanged;
0
- AppContainerList &inactiveApps;
0
- map<string, time_t> &restartFileTimes;
0
- bool needsRestart(const string &appRoot) {
0
- string restartFile(appRoot);
0
- restartFile.append("/tmp/restart.txt");
0
- if (stat(restartFile.c_str(), &buf) == 0) {
0
- #ifdef TESTING_APPLICATION_POOL
0
- if (getenv("nextRestartTxtDeletionShouldFail") != NULL) {
0
- unsetenv("nextRestartTxtDeletionShouldFail");
0
- ret = unlink(restartFile.c_str());
0
- ret = unlink(restartFile.c_str());
0
- } while (ret == -1 && errno == EAGAIN);
0
- if (ret == 0 || errno == ENOENT) {
0
- restartFileTimes.erase(appRoot);
0
- map<string, time_t>::const_iterator it;
0
- it = restartFileTimes.find(appRoot);
0
- if (it == restartFileTimes.end()) {
0
- result = buf.st_mtime != restartFileTimes[appRoot];
0
- restartFileTimes[appRoot] = buf.st_mtime;
0
- restartFileTimes.erase(appRoot);
0
- void cleanerThreadMainLoop() {
0
- mutex::scoped_lock l(lock);
0
- xtime_get(&xt, TIME_UTC);
0
- xt.sec += maxIdleTime + 1;
0
- if (cleanerThreadSleeper.timed_wait(l, xt)) {
0
- // Condition was woken up.
0
- // StandardApplicationPool is being destroyed.
0
- time_t now = time(NULL);
0
- AppContainerList::iterator it;
0
- for (it = inactiveApps.begin(); it != inactiveApps.end(); it++) {
0
- AppContainer &container(*it->get());
0
- ApplicationPtr app(container.app);
0
- AppContainerListPtr appList(apps[app->getAppRoot()]);
0
- if (now - container.lastUsed > (time_t) maxIdleTime) {
0
- P_TRACE("Cleaning idle app " << app->getAppRoot() <<
0
- " (PID " << app->getPid() << ")");
0
- appList->erase(container.iterator);
0
- AppContainerList::iterator prev = it;
0
- inactiveApps.erase(it);
0
- if (appList->empty()) {
0
- apps.erase(app->getAppRoot());
0
- data->restartFileTimes.erase(app->getAppRoot());
0
- ApplicationMap::iterator it;
0
- for (it = apps.begin(); it != apps.end(); it++) {
0
- AppContainerList &list = *(it->second.get());
0
- AppContainerList::iterator it2;
0
- for (it2 = list.begin(); it2 != list.end(); it2++) {
0
- (*it2)->app->detach();
0
- pair<AppContainerPtr, AppContainerList *>
0
- spawnOrUseExisting(mutex::scoped_lock &l, const string &appRoot,
0
- bool lowerPrivilege, const string &lowestUser) {
0
- AppContainerPtr container;
0
- AppContainerList *list;
0
- ApplicationMap::iterator it(apps.find(appRoot));
0
- if (it != apps.end() && needsRestart(appRoot)) {
0
- AppContainerList::iterator it2;
0
- list = it->second.get();
0
- for (it2 = list->begin(); it2 != list->end(); it2++) {
0
- if (container->sessions == 0) {
0
- inactiveApps.erase(container->ia_iterator);
0
- list->erase(container->iterator);
0
- spawnManager.reload(appRoot);
0
- if (it != apps.end()) {
0
- list = it->second.get();
0
- if (list->front()->sessions == 0 || count >= max) {
0
- container = list->front();
0
- list->push_back(container);
0
- container->iterator = list->end();
0
- container->iterator--;
0
- if (container->sessions == 0) {
0
- inactiveApps.erase(container->ia_iterator);
0
- container = ptr(new AppContainer());
0
- container->app = spawnManager.spawn(appRoot,
0
- lowerPrivilege, lowestUser);
0
- container->sessions = 0;
0
- list->push_back(container);
0
- container->iterator = list->end();
0
- container->iterator--;
0
- activeOrMaxChanged.notify_all();
0
- while (active >= max) {
0
- activeOrMaxChanged.wait(l);
0
- container = inactiveApps.front();
0
- inactiveApps.pop_front();
0
- list = apps[container->app->getAppRoot()].get();
0
- list->erase(container->iterator);
0
- apps.erase(container->app->getAppRoot());
0
- restartFileTimes.erase(container->app->getAppRoot());
0
- container = ptr(new AppContainer());
0
- container->app = spawnManager.spawn(appRoot, lowerPrivilege, lowestUser);
0
- container->sessions = 0;
0
- it = apps.find(appRoot);
0
- if (it == apps.end()) {
0
- list = new AppContainerList();
0
- apps[appRoot] = ptr(list);
0
- list = it->second.get();
0
- list->push_back(container);
0
- container->iterator = list->end();
0
- container->iterator--;
0
- activeOrMaxChanged.notify_all();
0
- } catch (const SpawnException &e) {
0
- string message("Cannot spawn application '");
0
- message.append(appRoot);
0
- message.append("': ");
0
- message.append(e.what());
0
- if (e.hasErrorPage()) {
0
- throw SpawnException(message, e.getErrorPage());
0
- throw SpawnException(message);
0
- } catch (const exception &e) {
0
- string message("Cannot spawn application '");
0
- message.append(appRoot);
0
- message.append("': ");
0
- message.append(e.what());
0
- throw SpawnException(message);
0
- return make_pair(container, list);
0
- * Create a new StandardApplicationPool object.
0
- * @param spawnServerCommand The filename of the spawn server to use.
0
- * @param logFile Specify a log file that the spawn server should use.
0
- * Messages on its standard output and standard error channels
0
- * will be written to this log file. If an empty string is
0
- * specified, no log file will be used, and the spawn server
0
- * will use the same standard output/error channels as the
0
- * @param environment The RAILS_ENV environment that all RoR applications
0
- * should use. If an empty string is specified, the current value
0
- * of the RAILS_ENV environment variable will be used.
0
- * @param rubyCommand The Ruby interpreter's command.
0
- * @throws SystemException An error occured while trying to setup the spawn server.
0
- * @throws IOException The specified log file could not be opened.
0
- StandardApplicationPool(const string &spawnServerCommand,
0
- const string &logFile = "",
0
- const string &environment = "production",
0
- const string &rubyCommand = "ruby")
0
- #ifndef PASSENGER_USE_DUMMY_SPAWN_MANAGER
0
- spawnManager(spawnServerCommand, logFile, environment, rubyCommand),
0
- data(new SharedData()),
0
- activeOrMaxChanged(data->activeOrMaxChanged),
0
- inactiveApps(data->inactiveApps),
0
- restartFileTimes(data->restartFileTimes)
0
- max = DEFAULT_MAX_POOL_SIZE;
0
- maxIdleTime = DEFAULT_MAX_IDLE_TIME;
0
- cleanerThread = new thread(bind(&StandardApplicationPool::cleanerThreadMainLoop, this));
0
- virtual ~StandardApplicationPool() {
0
- mutex::scoped_lock l(lock);
0
- cleanerThreadSleeper.notify_one();
0
- cleanerThread->join();
0
- virtual Application::SessionPtr
0
- get(const string &appRoot, bool lowerPrivilege = true, const string &lowestUser = "nobody") {
0
- const unsigned int MAX_ATTEMPTS = 5;
0
- mutex::scoped_lock l(lock);
0
- pair<AppContainerPtr, AppContainerList *> p(
0
- spawnOrUseExisting(l, appRoot, lowerPrivilege, lowestUser)
0
- AppContainerPtr &container(p.first);
0
- AppContainerList &list(*p.second);
0
- container->lastUsed = time(NULL);
0
- container->sessions++;
0
- return container->app->connect(SessionCloseCallback(data, container));
0
- } catch (const exception &e) {
0
- container->sessions--;
0
- if (attempt == MAX_ATTEMPTS) {
0
- string message("Cannot connect to an existing application instance for '");
0
- message.append(appRoot);
0
- message.append("': ");
0
- const SystemException &syse = dynamic_cast<const SystemException &>(e);
0
- message.append(syse.sys());
0
- } catch (const bad_cast &) {
0
- message.append(e.what());
0
- throw IOException(message);
0
- list.erase(container->iterator);
0
- // Never reached; shut up compiler warning
0
- return Application::SessionPtr();
0
- virtual void clear() {
0
- mutex::scoped_lock l(lock);
0
- restartFileTimes.clear();
0
- virtual void setMaxIdleTime(unsigned int seconds) {
0
- mutex::scoped_lock l(lock);
0
- maxIdleTime = seconds;
0
- cleanerThreadSleeper.notify_one();
0
- virtual void setMax(unsigned int max) {
0
- mutex::scoped_lock l(lock);
0
- activeOrMaxChanged.notify_all();
0
- virtual unsigned int getActive() const {
0
- virtual unsigned int getCount() const {
0
- virtual pid_t getSpawnServerPid() const {
0
- return spawnManager.getServerPid();
0
typedef shared_ptr<ApplicationPool> ApplicationPoolPtr;
0
}; // namespace Passenger
Comments
No one has commented yet.