StandardApplicationPool.h

00001 /*
00002  *  Phusion Passenger - http://www.modrails.com/
00003  *  Copyright (C) 2008  Phusion
00004  *
00005  *  This program is free software; you can redistribute it and/or modify
00006  *  it under the terms of the GNU General Public License as published by
00007  *  the Free Software Foundation; version 2 of the License.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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  *  See "doc/ApplicationPool algorithm.txt" for a more readable
00059  *  and detailed description of the algorithm implemented here.
00060  *
00061  ****************************************************************/
00062 
00063 /**
00064  * A standard implementation of ApplicationPool for single-process environments.
00065  *
00066  * The environment may or may not be multithreaded - StandardApplicationPool is completely
00067  * thread-safe. Apache with the threading MPM is an example of a multithreaded single-process
00068  * environment.
00069  *
00070  * This class is unusable in multi-process environments such as Apache with the prefork MPM.
00071  * The reasons are as follows:
00072  *  - StandardApplicationPool uses threads internally. Because threads disappear after a fork(),
00073  *    a StandardApplicationPool object will become unusable after a fork().
00074  *  - StandardApplicationPool stores its internal cache on the heap. Different processes
00075  *    cannot share their heaps, so they will not be able to access each others' pool cache.
00076  *  - StandardApplicationPool has a connection to the spawn server. If there are multiple
00077  *    processes, and they all use the spawn servers's connection at the same time without
00078  *    some sort of synchronization, then bad things will happen.
00079  *
00080  * (Of course, StandardApplicationPool <em>is</em> usable if each process creates its own
00081  * StandardApplicationPool object, but that would defeat the point of having a shared pool.)
00082  *
00083  * For multi-process environments, one should use ApplicationPoolServer instead.
00084  *
00085  * @ingroup Support
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         // Shortcuts for instance variables in SharedData. Saves typing in get().
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                                 // Condition was woken up.
00232                                 if (done) {
00233                                         // StandardApplicationPool is being destroyed.
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          * Create a new StandardApplicationPool object.
00389          *
00390          * @param spawnServerCommand The filename of the spawn server to use.
00391          * @param logFile Specify a log file that the spawn server should use.
00392          *            Messages on its standard output and standard error channels
00393          *            will be written to this log file. If an empty string is
00394          *            specified, no log file will be used, and the spawn server
00395          *            will use the same standard output/error channels as the
00396          *            current process.
00397          * @param environment The RAILS_ENV environment that all RoR applications
00398          *            should use. If an empty string is specified, the current value
00399          *            of the RAILS_ENV environment variable will be used.
00400          * @param rubyCommand The Ruby interpreter's command.
00401          * @param user The user that the spawn manager should run as. This
00402          *             parameter only has effect if the current process is
00403          *             running as root. If the empty string is given, or if
00404          *             the <tt>user</tt> is not a valid username, then
00405          *             the spawn manager will be run as the current user.
00406          * @param rubyCommand The Ruby interpreter's command.
00407          * @throws SystemException An error occured while trying to setup the spawn server.
00408          * @throws IOException The specified log file could not be opened.
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                 // Never reached; shut up compiler warning
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 } // namespace Passenger
00532 
00533 #endif /* _PASSENGER_STANDARD_APPLICATION_POOL_H_ */
00534 

Generated on Fri Apr 11 18:16:04 2008 for Passenger by  doxygen 1.5.3