00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #ifndef _PASSENGER_STANDARD_APPLICATION_POOL_H_
00021 #define _PASSENGER_STANDARD_APPLICATION_POOL_H_
00022
00023 #include <boost/shared_ptr.hpp>
00024 #include <boost/weak_ptr.hpp>
00025 #include <boost/thread.hpp>
00026 #include <boost/bind.hpp>
00027 #include <boost/date_time/microsec_time_clock.hpp>
00028 #include <boost/date_time/posix_time/posix_time.hpp>
00029
00030 #include <string>
00031 #include <sstream>
00032 #include <map>
00033 #include <list>
00034
00035 #include <sys/types.h>
00036 #include <sys/stat.h>
00037 #include <stdio.h>
00038 #include <unistd.h>
00039 #include <ctime>
00040 #include <cerrno>
00041 #ifdef TESTING_APPLICATION_POOL
00042 #include <cstdlib>
00043 #endif
00044
00045 #include "ApplicationPool.h"
00046 #include "Logging.h"
00047 #include "System.h"
00048 #ifdef PASSENGER_USE_DUMMY_SPAWN_MANAGER
00049 #include "DummySpawnManager.h"
00050 #else
00051 #include "SpawnManager.h"
00052 #endif
00053
00054 namespace Passenger {
00055
00056 using namespace std;
00057 using namespace boost;
00058
00059 class ApplicationPoolServer;
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
00088
00089
00090
00091
00092 class StandardApplicationPool: public ApplicationPool {
00093 private:
00094 static const int DEFAULT_MAX_IDLE_TIME = 120;
00095 static const int DEFAULT_MAX_POOL_SIZE = 20;
00096 static const int DEFAULT_MAX_INSTANCES_PER_APP = 0;
00097 static const int CLEANER_THREAD_STACK_SIZE = 1024 * 128;
00098 static const unsigned int MAX_GET_ATTEMPTS = 10;
00099 static const unsigned int GET_TIMEOUT = 5000;
00100
00101 friend class ApplicationPoolServer;
00102 struct AppContainer;
00103
00104 typedef shared_ptr<AppContainer> AppContainerPtr;
00105 typedef list<AppContainerPtr> AppContainerList;
00106 typedef shared_ptr<AppContainerList> AppContainerListPtr;
00107 typedef map<string, AppContainerListPtr> ApplicationMap;
00108
00109 struct AppContainer {
00110 ApplicationPtr app;
00111 time_t lastUsed;
00112 unsigned int sessions;
00113 AppContainerList::iterator iterator;
00114 AppContainerList::iterator ia_iterator;
00115 };
00116
00117 struct SharedData {
00118 mutex lock;
00119 condition activeOrMaxChanged;
00120
00121 ApplicationMap apps;
00122 unsigned int max;
00123 unsigned int count;
00124 unsigned int active;
00125 unsigned int maxPerApp;
00126 AppContainerList inactiveApps;
00127 map<string, time_t> restartFileTimes;
00128 map<string, unsigned int> appInstanceCount;
00129 };
00130
00131 typedef shared_ptr<SharedData> SharedDataPtr;
00132
00133 struct SessionCloseCallback {
00134 SharedDataPtr data;
00135 weak_ptr<AppContainer> container;
00136
00137 SessionCloseCallback(SharedDataPtr data,
00138 const weak_ptr<AppContainer> &container) {
00139 this->data = data;
00140 this->container = container;
00141 }
00142
00143 void operator()() {
00144 mutex::scoped_lock l(data->lock);
00145 AppContainerPtr container(this->container.lock());
00146
00147 if (container == NULL) {
00148 return;
00149 }
00150
00151 ApplicationMap::iterator it;
00152 it = data->apps.find(container->app->getAppRoot());
00153 if (it != data->apps.end()) {
00154 AppContainerListPtr list(it->second);
00155 container->lastUsed = time(NULL);
00156 container->sessions--;
00157 if (container->sessions == 0) {
00158 list->erase(container->iterator);
00159 list->push_front(container);
00160 container->iterator = list->begin();
00161 data->inactiveApps.push_back(container);
00162 container->ia_iterator = data->inactiveApps.end();
00163 container->ia_iterator--;
00164 data->active--;
00165 data->activeOrMaxChanged.notify_all();
00166 }
00167 }
00168 }
00169 };
00170
00171 #ifdef PASSENGER_USE_DUMMY_SPAWN_MANAGER
00172 DummySpawnManager spawnManager;
00173 #else
00174 SpawnManager spawnManager;
00175 #endif
00176 SharedDataPtr data;
00177 thread *cleanerThread;
00178 bool detached;
00179 bool done;
00180 unsigned int maxIdleTime;
00181 condition cleanerThreadSleeper;
00182
00183
00184 mutex &lock;
00185 condition &activeOrMaxChanged;
00186 ApplicationMap &apps;
00187 unsigned int &max;
00188 unsigned int &count;
00189 unsigned int &active;
00190 unsigned int &maxPerApp;
00191 AppContainerList &inactiveApps;
00192 map<string, time_t> &restartFileTimes;
00193 map<string, unsigned int> &appInstanceCount;
00194
00195
00196
00197
00198 bool inline verifyState() {
00199 #if PASSENGER_DEBUG
00200
00201 ApplicationMap::const_iterator it;
00202 for (it = apps.begin(); it != apps.end(); it++) {
00203 AppContainerList *list = it->second.get();
00204 P_ASSERT(!list->empty(), false, "List for '" << it->first << "' is nonempty.");
00205
00206 AppContainerList::const_iterator prev_lit;
00207 AppContainerList::const_iterator lit;
00208 prev_lit = list->begin();
00209 lit = prev_lit;
00210 lit++;
00211 for (; lit != list->end(); lit++) {
00212 if ((*prev_lit)->sessions > 0) {
00213 P_ASSERT((*lit)->sessions > 0, false,
00214 "List for '" << it->first <<
00215 "' is sorted from nonactive to active");
00216 }
00217 }
00218 }
00219
00220 P_ASSERT(active <= count, false,
00221 "active (" << active << ") < count (" << count << ")");
00222 P_ASSERT(inactiveApps.size() == count - active, false,
00223 "inactive_apps.size() == count - active");
00224 #endif
00225 return true;
00226 }
00227
00228 template<typename LockActionType>
00229 string toString(LockActionType lockAction) const {
00230 unique_lock<mutex> l(lock, lockAction);
00231 stringstream result;
00232
00233 result << "----------- General information -----------" << endl;
00234 result << "max = " << max << endl;
00235 result << "count = " << count << endl;
00236 result << "active = " << active << endl;
00237 result << "inactive = " << inactiveApps.size() << endl;
00238 result << endl;
00239
00240 result << "----------- Applications -----------" << endl;
00241 ApplicationMap::const_iterator it;
00242 for (it = apps.begin(); it != apps.end(); it++) {
00243 AppContainerList *list = it->second.get();
00244 AppContainerList::const_iterator lit;
00245
00246 result << it->first << ": " << endl;
00247 for (lit = list->begin(); lit != list->end(); lit++) {
00248 AppContainer *container = lit->get();
00249 char buf[128];
00250
00251 snprintf(buf, sizeof(buf), "PID: %-8d Sessions: %d",
00252 container->app->getPid(), container->sessions);
00253 result << " " << buf << endl;
00254 }
00255 result << endl;
00256 }
00257 return result.str();
00258 }
00259
00260 bool needsRestart(const string &appRoot) {
00261 string restartFile(appRoot);
00262 restartFile.append("/tmp/restart.txt");
00263
00264 struct stat buf;
00265 bool result;
00266 int ret;
00267
00268 do {
00269 ret = stat(restartFile.c_str(), &buf);
00270 } while (ret == -1 && errno == EINTR);
00271 if (ret == 0) {
00272 do {
00273 ret = unlink(restartFile.c_str());
00274 } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
00275 if (ret == 0 || errno == ENOENT) {
00276 restartFileTimes.erase(appRoot);
00277 result = true;
00278 } else {
00279 map<string, time_t>::const_iterator it;
00280
00281 it = restartFileTimes.find(appRoot);
00282 if (it == restartFileTimes.end()) {
00283 result = true;
00284 } else {
00285 result = buf.st_mtime != restartFileTimes[appRoot];
00286 }
00287 restartFileTimes[appRoot] = buf.st_mtime;
00288 }
00289 } else {
00290 restartFileTimes.erase(appRoot);
00291 result = false;
00292 }
00293 return result;
00294 }
00295
00296 void cleanerThreadMainLoop() {
00297 this_thread::disable_syscall_interruption dsi;
00298 unique_lock<mutex> l(lock);
00299 try {
00300 while (!done && !this_thread::interruption_requested()) {
00301 xtime xt;
00302 xtime_get(&xt, TIME_UTC);
00303 xt.sec += maxIdleTime + 1;
00304 if (cleanerThreadSleeper.timed_wait(l, xt)) {
00305
00306 if (done) {
00307
00308 break;
00309 } else {
00310
00311 continue;
00312 }
00313 }
00314
00315 time_t now = InterruptableCalls::time(NULL);
00316 AppContainerList::iterator it;
00317 for (it = inactiveApps.begin(); it != inactiveApps.end(); it++) {
00318 AppContainer &container(*it->get());
00319 ApplicationPtr app(container.app);
00320 AppContainerListPtr appList(apps[app->getAppRoot()]);
00321
00322 if (now - container.lastUsed > (time_t) maxIdleTime) {
00323 P_DEBUG("Cleaning idle app " << app->getAppRoot() <<
00324 " (PID " << app->getPid() << ")");
00325 appList->erase(container.iterator);
00326
00327 AppContainerList::iterator prev = it;
00328 prev--;
00329 inactiveApps.erase(it);
00330 it = prev;
00331
00332 appInstanceCount[app->getAppRoot()]--;
00333
00334 count--;
00335 }
00336 if (appList->empty()) {
00337 apps.erase(app->getAppRoot());
00338 appInstanceCount.erase(app->getAppRoot());
00339 data->restartFileTimes.erase(app->getAppRoot());
00340 }
00341 }
00342 }
00343 } catch (const exception &e) {
00344 P_ERROR("Uncaught exception: " << e.what());
00345 }
00346 }
00347
00348
00349
00350
00351
00352
00353 pair<AppContainerPtr, AppContainerList *>
00354 spawnOrUseExisting(
00355 mutex::scoped_lock &l,
00356 const string &appRoot,
00357 bool lowerPrivilege,
00358 const string &lowestUser,
00359 const string &environment,
00360 const string &spawnMethod,
00361 const string &appType
00362 ) {
00363 this_thread::disable_interruption di;
00364 this_thread::disable_syscall_interruption dsi;
00365 AppContainerPtr container;
00366 AppContainerList *list;
00367
00368 try {
00369 ApplicationMap::iterator it(apps.find(appRoot));
00370
00371 if (it != apps.end() && needsRestart(appRoot)) {
00372 AppContainerList::iterator it2;
00373 list = it->second.get();
00374 for (it2 = list->begin(); it2 != list->end(); it2++) {
00375 container = *it2;
00376 if (container->sessions == 0) {
00377 inactiveApps.erase(container->ia_iterator);
00378 } else {
00379 active--;
00380 }
00381 it2--;
00382 list->erase(container->iterator);
00383 count--;
00384 }
00385 apps.erase(appRoot);
00386 appInstanceCount.erase(appRoot);
00387 spawnManager.reload(appRoot);
00388 it = apps.end();
00389 activeOrMaxChanged.notify_all();
00390 }
00391
00392 if (it != apps.end()) {
00393 list = it->second.get();
00394
00395 if (list->front()->sessions == 0) {
00396 container = list->front();
00397 list->pop_front();
00398 list->push_back(container);
00399 container->iterator = list->end();
00400 container->iterator--;
00401 inactiveApps.erase(container->ia_iterator);
00402 active++;
00403 activeOrMaxChanged.notify_all();
00404 } else if (count >= max || (
00405 maxPerApp != 0 && appInstanceCount[appRoot] >= maxPerApp )
00406 ) {
00407 AppContainerList::iterator it(list->begin());
00408 AppContainerList::iterator smallest(list->begin());
00409 it++;
00410 for (; it != list->end(); it++) {
00411 if ((*it)->sessions < (*smallest)->sessions) {
00412 smallest = it;
00413 }
00414 }
00415 container = *smallest;
00416 list->erase(smallest);
00417 list->push_back(container);
00418 container->iterator = list->end();
00419 container->iterator--;
00420 } else {
00421 container = ptr(new AppContainer());
00422 {
00423 this_thread::restore_interruption ri(di);
00424 this_thread::restore_syscall_interruption rsi(dsi);
00425 container->app = spawnManager.spawn(appRoot,
00426 lowerPrivilege, lowestUser, environment,
00427 spawnMethod, appType);
00428 }
00429 container->sessions = 0;
00430 list->push_back(container);
00431 container->iterator = list->end();
00432 container->iterator--;
00433 appInstanceCount[appRoot]++;
00434 count++;
00435 active++;
00436 activeOrMaxChanged.notify_all();
00437 }
00438 } else {
00439 while (!(
00440 active < max &&
00441 (maxPerApp == 0 || appInstanceCount[appRoot] < maxPerApp)
00442 )) {
00443 activeOrMaxChanged.wait(l);
00444 }
00445 if (count == max) {
00446 container = inactiveApps.front();
00447 inactiveApps.pop_front();
00448 list = apps[container->app->getAppRoot()].get();
00449 list->erase(container->iterator);
00450 if (list->empty()) {
00451 apps.erase(container->app->getAppRoot());
00452 restartFileTimes.erase(container->app->getAppRoot());
00453 appInstanceCount.erase(container->app->getAppRoot());
00454 } else {
00455 appInstanceCount[container->app->getAppRoot()]--;
00456 }
00457 count--;
00458 }
00459 container = ptr(new AppContainer());
00460 {
00461 this_thread::restore_interruption ri(di);
00462 this_thread::restore_syscall_interruption rsi(dsi);
00463 container->app = spawnManager.spawn(appRoot, lowerPrivilege, lowestUser,
00464 environment, spawnMethod, appType);
00465 }
00466 container->sessions = 0;
00467 it = apps.find(appRoot);
00468 if (it == apps.end()) {
00469 list = new AppContainerList();
00470 apps[appRoot] = ptr(list);
00471 appInstanceCount[appRoot] = 1;
00472 } else {
00473 list = it->second.get();
00474 appInstanceCount[appRoot]++;
00475 }
00476 list->push_back(container);
00477 container->iterator = list->end();
00478 container->iterator--;
00479 count++;
00480 active++;
00481 activeOrMaxChanged.notify_all();
00482 }
00483 } catch (const SpawnException &e) {
00484 string message("Cannot spawn application '");
00485 message.append(appRoot);
00486 message.append("': ");
00487 message.append(e.what());
00488 if (e.hasErrorPage()) {
00489 throw SpawnException(message, e.getErrorPage());
00490 } else {
00491 throw SpawnException(message);
00492 }
00493 } catch (const exception &e) {
00494 string message("Cannot spawn application '");
00495 message.append(appRoot);
00496 message.append("': ");
00497 message.append(e.what());
00498 throw SpawnException(message);
00499 }
00500
00501 return make_pair(container, list);
00502 }
00503
00504 public:
00505
00506
00507
00508
00509
00510
00511
00512
00513
00514
00515
00516
00517
00518
00519
00520
00521
00522
00523
00524
00525 StandardApplicationPool(const string &spawnServerCommand,
00526 const string &logFile = "",
00527 const string &rubyCommand = "ruby",
00528 const string &user = "")
00529 :
00530 #ifndef PASSENGER_USE_DUMMY_SPAWN_MANAGER
00531 spawnManager(spawnServerCommand, logFile, rubyCommand, user),
00532 #endif
00533 data(new SharedData()),
00534 lock(data->lock),
00535 activeOrMaxChanged(data->activeOrMaxChanged),
00536 apps(data->apps),
00537 max(data->max),
00538 count(data->count),
00539 active(data->active),
00540 maxPerApp(data->maxPerApp),
00541 inactiveApps(data->inactiveApps),
00542 restartFileTimes(data->restartFileTimes),
00543 appInstanceCount(data->appInstanceCount)
00544 {
00545 detached = false;
00546 done = false;
00547 max = DEFAULT_MAX_POOL_SIZE;
00548 count = 0;
00549 active = 0;
00550 maxPerApp = DEFAULT_MAX_INSTANCES_PER_APP;
00551 maxIdleTime = DEFAULT_MAX_IDLE_TIME;
00552 cleanerThread = new thread(
00553 bind(&StandardApplicationPool::cleanerThreadMainLoop, this),
00554 CLEANER_THREAD_STACK_SIZE
00555 );
00556 }
00557
00558 virtual ~StandardApplicationPool() {
00559 if (!detached) {
00560 this_thread::disable_interruption di;
00561 {
00562 mutex::scoped_lock l(lock);
00563 done = true;
00564 cleanerThreadSleeper.notify_one();
00565 }
00566 cleanerThread->join();
00567 }
00568 delete cleanerThread;
00569 }
00570
00571 virtual Application::SessionPtr get(
00572 const string &appRoot,
00573 bool lowerPrivilege = true,
00574 const string &lowestUser = "nobody",
00575 const string &environment = "production",
00576 const string &spawnMethod = "smart",
00577 const string &appType = "rails"
00578 ) {
00579 using namespace boost::posix_time;
00580 unsigned int attempt = 0;
00581 ptime timeLimit(get_system_time() + millisec(GET_TIMEOUT));
00582 unique_lock<mutex> l(lock);
00583
00584 while (true) {
00585 attempt++;
00586
00587 pair<AppContainerPtr, AppContainerList *> p(
00588 spawnOrUseExisting(l, appRoot, lowerPrivilege, lowestUser,
00589 environment, spawnMethod, appType)
00590 );
00591 AppContainerPtr &container(p.first);
00592 AppContainerList &list(*p.second);
00593
00594 container->lastUsed = time(NULL);
00595 container->sessions++;
00596
00597 P_ASSERT(verifyState(), Application::SessionPtr(),
00598 "State is valid:\n" << toString(false));
00599 try {
00600 return container->app->connect(SessionCloseCallback(data, container));
00601 } catch (const exception &e) {
00602 container->sessions--;
00603 if (attempt == MAX_GET_ATTEMPTS) {
00604 string message("Cannot connect to an existing "
00605 "application instance for '");
00606 message.append(appRoot);
00607 message.append("': ");
00608 try {
00609 const SystemException &syse =
00610 dynamic_cast<const SystemException &>(e);
00611 message.append(syse.sys());
00612 } catch (const bad_cast &) {
00613 message.append(e.what());
00614 }
00615 throw IOException(message);
00616 } else {
00617 list.erase(container->iterator);
00618 if (list.empty()) {
00619 apps.erase(appRoot);
00620 appInstanceCount.erase(appRoot);
00621 }
00622 count--;
00623 active--;
00624 activeOrMaxChanged.notify_all();
00625 P_ASSERT(verifyState(), Application::SessionPtr(),
00626 "State is valid.");
00627 }
00628 }
00629 }
00630
00631 return Application::SessionPtr();
00632 }
00633
00634 virtual void clear() {
00635 mutex::scoped_lock l(lock);
00636 apps.clear();
00637 inactiveApps.clear();
00638 restartFileTimes.clear();
00639 appInstanceCount.clear();
00640 count = 0;
00641 active = 0;
00642 }
00643
00644 virtual void setMaxIdleTime(unsigned int seconds) {
00645 mutex::scoped_lock l(lock);
00646 maxIdleTime = seconds;
00647 cleanerThreadSleeper.notify_one();
00648 }
00649
00650 virtual void setMax(unsigned int max) {
00651 mutex::scoped_lock l(lock);
00652 this->max = max;
00653 activeOrMaxChanged.notify_all();
00654 }
00655
00656 virtual unsigned int getActive() const {
00657 return active;
00658 }
00659
00660 virtual unsigned int getCount() const {
00661 return count;
00662 }
00663
00664 virtual void setMaxPerApp(unsigned int maxPerApp) {
00665 mutex::scoped_lock l(lock);
00666 this->maxPerApp = maxPerApp;
00667 activeOrMaxChanged.notify_all();
00668 }
00669
00670 virtual pid_t getSpawnServerPid() const {
00671 return spawnManager.getServerPid();
00672 }
00673
00674
00675
00676
00677
00678 virtual string toString(bool lockMutex = true) const {
00679 if (lockMutex) {
00680 return toString(boost::adopt_lock);
00681 } else {
00682 return toString(boost::defer_lock);
00683 }
00684 }
00685 };
00686
00687 }
00688
00689 #endif
00690