00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #ifndef _PASSENGER_STANDARD_APPLICATION_POOL_H_
00019 #define _PASSENGER_STANDARD_APPLICATION_POOL_H_
00020
00021 #include <boost/shared_ptr.hpp>
00022 #include <boost/weak_ptr.hpp>
00023 #include <boost/thread.hpp>
00024 #include <boost/thread/mutex.hpp>
00025 #include <boost/thread/condition.hpp>
00026 #include <boost/bind.hpp>
00027
00028 #include <string>
00029 #include <map>
00030 #include <list>
00031
00032 #include <sys/types.h>
00033 #include <sys/stat.h>
00034 #include <unistd.h>
00035 #include <ctime>
00036 #include <cerrno>
00037 #ifdef TESTING_APPLICATION_POOL
00038 #include <cstdlib>
00039 #endif
00040
00041 #include "ApplicationPool.h"
00042 #include "Logging.h"
00043 #ifdef PASSENGER_USE_DUMMY_SPAWN_MANAGER
00044 #include "DummySpawnManager.h"
00045 #else
00046 #include "SpawnManager.h"
00047 #endif
00048
00049 namespace Passenger {
00050
00051 using namespace std;
00052 using namespace boost;
00053
00054 class ApplicationPoolServer;
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087 class StandardApplicationPool: public ApplicationPool {
00088 private:
00089 static const int DEFAULT_MAX_IDLE_TIME = 120;
00090 static const int DEFAULT_MAX_POOL_SIZE = 20;
00091
00092 friend class ApplicationPoolServer;
00093 struct AppContainer;
00094
00095 typedef shared_ptr<AppContainer> AppContainerPtr;
00096 typedef list<AppContainerPtr> AppContainerList;
00097 typedef shared_ptr<AppContainerList> AppContainerListPtr;
00098 typedef map<string, AppContainerListPtr> ApplicationMap;
00099
00100 struct AppContainer {
00101 ApplicationPtr app;
00102 time_t lastUsed;
00103 unsigned int sessions;
00104 AppContainerList::iterator iterator;
00105 AppContainerList::iterator ia_iterator;
00106 };
00107
00108 struct SharedData {
00109 mutex lock;
00110 condition activeOrMaxChanged;
00111
00112 ApplicationMap apps;
00113 unsigned int max;
00114 unsigned int count;
00115 unsigned int active;
00116 AppContainerList inactiveApps;
00117 map<string, time_t> restartFileTimes;
00118 };
00119
00120 typedef shared_ptr<SharedData> SharedDataPtr;
00121
00122 struct SessionCloseCallback {
00123 SharedDataPtr data;
00124 weak_ptr<AppContainer> container;
00125
00126 SessionCloseCallback(SharedDataPtr data,
00127 const weak_ptr<AppContainer> &container) {
00128 this->data = data;
00129 this->container = container;
00130 }
00131
00132 void operator()() {
00133 mutex::scoped_lock l(data->lock);
00134 AppContainerPtr container(this->container.lock());
00135
00136 if (container == NULL) {
00137 return;
00138 }
00139
00140 ApplicationMap::iterator it;
00141 it = data->apps.find(container->app->getAppRoot());
00142 if (it != data->apps.end()) {
00143 AppContainerListPtr list(it->second);
00144 container->lastUsed = time(NULL);
00145 container->sessions--;
00146 if (container->sessions == 0) {
00147 list->erase(container->iterator);
00148 list->push_front(container);
00149 container->iterator = list->begin();
00150 data->inactiveApps.push_back(container);
00151 container->ia_iterator = data->inactiveApps.end();
00152 container->ia_iterator--;
00153 }
00154 data->active--;
00155 data->activeOrMaxChanged.notify_all();
00156 }
00157 }
00158 };
00159
00160 #ifdef PASSENGER_USE_DUMMY_SPAWN_MANAGER
00161 DummySpawnManager spawnManager;
00162 #else
00163 SpawnManager spawnManager;
00164 #endif
00165 SharedDataPtr data;
00166 thread *cleanerThread;
00167 bool detached;
00168 bool done;
00169 unsigned int maxIdleTime;
00170 condition cleanerThreadSleeper;
00171
00172
00173 mutex &lock;
00174 condition &activeOrMaxChanged;
00175 ApplicationMap &apps;
00176 unsigned int &max;
00177 unsigned int &count;
00178 unsigned int &active;
00179 AppContainerList &inactiveApps;
00180 map<string, time_t> &restartFileTimes;
00181
00182 bool needsRestart(const string &appRoot) {
00183 string restartFile(appRoot);
00184 restartFile.append("/tmp/restart.txt");
00185
00186 struct stat buf;
00187 bool result;
00188 if (stat(restartFile.c_str(), &buf) == 0) {
00189 int ret;
00190 #ifdef TESTING_APPLICATION_POOL
00191 if (getenv("nextRestartTxtDeletionShouldFail") != NULL) {
00192 unsetenv("nextRestartTxtDeletionShouldFail");
00193 ret = -1;
00194 errno = EACCES;
00195 } else {
00196 ret = unlink(restartFile.c_str());
00197 }
00198 #else
00199 do {
00200 ret = unlink(restartFile.c_str());
00201 } while (ret == -1 && errno == EAGAIN);
00202 #endif
00203 if (ret == 0 || errno == ENOENT) {
00204 restartFileTimes.erase(appRoot);
00205 result = true;
00206 } else {
00207 map<string, time_t>::const_iterator it;
00208
00209 it = restartFileTimes.find(appRoot);
00210 if (it == restartFileTimes.end()) {
00211 result = true;
00212 } else {
00213 result = buf.st_mtime != restartFileTimes[appRoot];
00214 }
00215 restartFileTimes[appRoot] = buf.st_mtime;
00216 }
00217 } else {
00218 restartFileTimes.erase(appRoot);
00219 result = false;
00220 }
00221 return result;
00222 }
00223
00224 void cleanerThreadMainLoop() {
00225 mutex::scoped_lock l(lock);
00226 while (!done) {
00227 xtime xt;
00228 xtime_get(&xt, TIME_UTC);
00229 xt.sec += maxIdleTime + 1;
00230 if (cleanerThreadSleeper.timed_wait(l, xt)) {
00231
00232 if (done) {
00233
00234 break;
00235 } else {
00236 continue;
00237 }
00238 }
00239
00240 time_t now = time(NULL);
00241 AppContainerList::iterator it;
00242 for (it = inactiveApps.begin(); it != inactiveApps.end(); it++) {
00243 AppContainer &container(*it->get());
00244 ApplicationPtr app(container.app);
00245 AppContainerListPtr appList(apps[app->getAppRoot()]);
00246
00247 if (now - container.lastUsed > (time_t) maxIdleTime) {
00248 P_DEBUG("Cleaning idle app " << app->getAppRoot() <<
00249 " (PID " << app->getPid() << ")");
00250 appList->erase(container.iterator);
00251
00252 AppContainerList::iterator prev = it;
00253 prev--;
00254 inactiveApps.erase(it);
00255 it = prev;
00256
00257 count--;
00258 }
00259 if (appList->empty()) {
00260 apps.erase(app->getAppRoot());
00261 data->restartFileTimes.erase(app->getAppRoot());
00262 }
00263 }
00264 }
00265 }
00266
00267 void detach() {
00268 detached = true;
00269
00270 ApplicationMap::iterator it;
00271 for (it = apps.begin(); it != apps.end(); it++) {
00272 AppContainerList &list = *(it->second.get());
00273 AppContainerList::iterator it2;
00274 for (it2 = list.begin(); it2 != list.end(); it2++) {
00275 (*it2)->app->detach();
00276 }
00277 }
00278 }
00279
00280 pair<AppContainerPtr, AppContainerList *>
00281 spawnOrUseExisting(mutex::scoped_lock &l, const string &appRoot,
00282 bool lowerPrivilege, const string &lowestUser) {
00283 AppContainerPtr container;
00284 AppContainerList *list;
00285
00286 try {
00287 ApplicationMap::iterator it(apps.find(appRoot));
00288
00289 if (it != apps.end() && needsRestart(appRoot)) {
00290 AppContainerList::iterator it2;
00291 list = it->second.get();
00292 for (it2 = list->begin(); it2 != list->end(); it2++) {
00293 container = *it2;
00294 if (container->sessions == 0) {
00295 inactiveApps.erase(container->ia_iterator);
00296 } else {
00297 active--;
00298 }
00299 it2--;
00300 list->erase(container->iterator);
00301 count--;
00302 }
00303 apps.erase(appRoot);
00304 spawnManager.reload(appRoot);
00305 it = apps.end();
00306 }
00307
00308 if (it != apps.end()) {
00309 list = it->second.get();
00310
00311 if (list->front()->sessions == 0 || count >= max) {
00312 container = list->front();
00313 list->pop_front();
00314 list->push_back(container);
00315 container->iterator = list->end();
00316 container->iterator--;
00317 if (container->sessions == 0) {
00318 inactiveApps.erase(container->ia_iterator);
00319 }
00320 active++;
00321 } else {
00322 container = ptr(new AppContainer());
00323 container->app = spawnManager.spawn(appRoot,
00324 lowerPrivilege, lowestUser);
00325 container->sessions = 0;
00326 list->push_back(container);
00327 container->iterator = list->end();
00328 container->iterator--;
00329 count++;
00330 active++;
00331 activeOrMaxChanged.notify_all();
00332 }
00333 } else {
00334 while (active >= max) {
00335 activeOrMaxChanged.wait(l);
00336 }
00337 if (count == max) {
00338 container = inactiveApps.front();
00339 inactiveApps.pop_front();
00340 list = apps[container->app->getAppRoot()].get();
00341 list->erase(container->iterator);
00342 if (list->empty()) {
00343 apps.erase(container->app->getAppRoot());
00344 restartFileTimes.erase(container->app->getAppRoot());
00345 }
00346 count--;
00347 }
00348 container = ptr(new AppContainer());
00349 container->app = spawnManager.spawn(appRoot, lowerPrivilege, lowestUser);
00350 container->sessions = 0;
00351 it = apps.find(appRoot);
00352 if (it == apps.end()) {
00353 list = new AppContainerList();
00354 apps[appRoot] = ptr(list);
00355 } else {
00356 list = it->second.get();
00357 }
00358 list->push_back(container);
00359 container->iterator = list->end();
00360 container->iterator--;
00361 count++;
00362 active++;
00363 activeOrMaxChanged.notify_all();
00364 }
00365 } catch (const SpawnException &e) {
00366 string message("Cannot spawn application '");
00367 message.append(appRoot);
00368 message.append("': ");
00369 message.append(e.what());
00370 if (e.hasErrorPage()) {
00371 throw SpawnException(message, e.getErrorPage());
00372 } else {
00373 throw SpawnException(message);
00374 }
00375 } catch (const exception &e) {
00376 string message("Cannot spawn application '");
00377 message.append(appRoot);
00378 message.append("': ");
00379 message.append(e.what());
00380 throw SpawnException(message);
00381 }
00382
00383 return make_pair(container, list);
00384 }
00385
00386 public:
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410 StandardApplicationPool(const string &spawnServerCommand,
00411 const string &logFile = "",
00412 const string &environment = "production",
00413 const string &rubyCommand = "ruby",
00414 const string &user = "")
00415 :
00416 #ifndef PASSENGER_USE_DUMMY_SPAWN_MANAGER
00417 spawnManager(spawnServerCommand, logFile, environment, rubyCommand, user),
00418 #endif
00419 data(new SharedData()),
00420 lock(data->lock),
00421 activeOrMaxChanged(data->activeOrMaxChanged),
00422 apps(data->apps),
00423 max(data->max),
00424 count(data->count),
00425 active(data->active),
00426 inactiveApps(data->inactiveApps),
00427 restartFileTimes(data->restartFileTimes)
00428 {
00429 detached = false;
00430 done = false;
00431 max = DEFAULT_MAX_POOL_SIZE;
00432 count = 0;
00433 active = 0;
00434 maxIdleTime = DEFAULT_MAX_IDLE_TIME;
00435 cleanerThread = new thread(bind(&StandardApplicationPool::cleanerThreadMainLoop, this));
00436 }
00437
00438 virtual ~StandardApplicationPool() {
00439 if (!detached) {
00440 {
00441 mutex::scoped_lock l(lock);
00442 done = true;
00443 cleanerThreadSleeper.notify_one();
00444 }
00445 cleanerThread->join();
00446 }
00447 delete cleanerThread;
00448 }
00449
00450 virtual Application::SessionPtr
00451 get(const string &appRoot, bool lowerPrivilege = true, const string &lowestUser = "nobody") {
00452 unsigned int attempt;
00453 const unsigned int MAX_ATTEMPTS = 5;
00454
00455 attempt = 0;
00456 while (true) {
00457 attempt++;
00458
00459 mutex::scoped_lock l(lock);
00460 pair<AppContainerPtr, AppContainerList *> p(
00461 spawnOrUseExisting(l, appRoot, lowerPrivilege, lowestUser)
00462 );
00463 AppContainerPtr &container(p.first);
00464 AppContainerList &list(*p.second);
00465
00466 container->lastUsed = time(NULL);
00467 container->sessions++;
00468 try {
00469 return container->app->connect(SessionCloseCallback(data, container));
00470 } catch (const exception &e) {
00471 container->sessions--;
00472 if (attempt == MAX_ATTEMPTS) {
00473 string message("Cannot connect to an existing application instance for '");
00474 message.append(appRoot);
00475 message.append("': ");
00476 try {
00477 const SystemException &syse = dynamic_cast<const SystemException &>(e);
00478 message.append(syse.sys());
00479 } catch (const bad_cast &) {
00480 message.append(e.what());
00481 }
00482 throw IOException(message);
00483 } else {
00484 list.erase(container->iterator);
00485 if (list.empty()) {
00486 apps.erase(appRoot);
00487 }
00488 count--;
00489 active--;
00490 }
00491 }
00492 }
00493
00494 return Application::SessionPtr();
00495 }
00496
00497 virtual void clear() {
00498 mutex::scoped_lock l(lock);
00499 apps.clear();
00500 inactiveApps.clear();
00501 restartFileTimes.clear();
00502 count = 0;
00503 active = 0;
00504 }
00505
00506 virtual void setMaxIdleTime(unsigned int seconds) {
00507 mutex::scoped_lock l(lock);
00508 maxIdleTime = seconds;
00509 cleanerThreadSleeper.notify_one();
00510 }
00511
00512 virtual void setMax(unsigned int max) {
00513 mutex::scoped_lock l(lock);
00514 this->max = max;
00515 activeOrMaxChanged.notify_all();
00516 }
00517
00518 virtual unsigned int getActive() const {
00519 return active;
00520 }
00521
00522 virtual unsigned int getCount() const {
00523 return count;
00524 }
00525
00526 virtual pid_t getSpawnServerPid() const {
00527 return spawnManager.getServerPid();
00528 }
00529 };
00530
00531 }
00532
00533 #endif
00534