SpawnManager.h

00001 /*
00002  *  Phusion Passenger - http://www.modrails.com/
00003  *  Copyright (C) 2008  Phusion
00004  *
00005  *  Phusion Passenger is a trademark of Hongli Lai & Ninh Bui.
00006  *
00007  *  This program is free software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; version 2 of the License.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00019  */
00020 #ifndef _PASSENGER_SPAWN_MANAGER_H_
00021 #define _PASSENGER_SPAWN_MANAGER_H_
00022 
00023 #include <string>
00024 #include <list>
00025 #include <boost/shared_ptr.hpp>
00026 #include <boost/thread.hpp>
00027 
00028 #include <sys/types.h>
00029 #include <sys/wait.h>
00030 #include <sys/stat.h>
00031 #include <arpa/inet.h>
00032 #include <cstdio>
00033 #include <cstdarg>
00034 #include <unistd.h>
00035 #include <errno.h>
00036 #include <pwd.h>
00037 #include <signal.h>
00038 
00039 #include "Application.h"
00040 #include "MessageChannel.h"
00041 #include "Exceptions.h"
00042 #include "Logging.h"
00043 #include "System.h"
00044 
00045 namespace Passenger {
00046 
00047 using namespace std;
00048 using namespace boost;
00049 
00050 /**
00051  * @brief Spawning of Ruby on Rails/Rack application instances.
00052  *
00053  * This class is responsible for spawning new instances of Ruby on Rails or
00054  * Rack applications. Use the spawn() method to do so.
00055  *
00056  * @note This class is fully thread-safe.
00057  *
00058  * <h2>Implementation details</h2>
00059  * Internally, this class makes use of a spawn server, which is written in Ruby. This server
00060  * is automatically started when a SpawnManager instance is created, and automatically
00061  * shutdown when that instance is destroyed. The existance of the spawn server is almost
00062  * totally transparent to users of this class. Spawn requests are sent to the server,
00063  * and details about the spawned process is returned.
00064  *
00065  * If the spawn server dies during the middle of an operation, it will be restarted.
00066  * See spawn() for full details.
00067  *
00068  * The communication channel with the server is anonymous, i.e. no other processes
00069  * can access the communication channel, so communication is guaranteed to be safe
00070  * (unless, of course, if the spawn server itself is a trojan).
00071  *
00072  * The server will try to keep the spawning time as small as possible, by keeping
00073  * corresponding Ruby on Rails frameworks and application code in memory. So the second
00074  * time an instance of the same application is spawned, the spawn time is significantly
00075  * lower than the first time. Nevertheless, spawning is a relatively expensive operation
00076  * (compared to the processing of a typical HTTP request/response), and so should be
00077  * avoided whenever possible.
00078  *
00079  * See the documentation of the spawn server for full implementation details.
00080  *
00081  * @ingroup Support
00082  */
00083 class SpawnManager {
00084 private:
00085         static const int SPAWN_SERVER_INPUT_FD = 3;
00086 
00087         string spawnServerCommand;
00088         string logFile;
00089         string rubyCommand;
00090         string user;
00091         
00092         mutex lock;
00093         
00094         MessageChannel channel;
00095         pid_t pid;
00096         bool serverNeedsRestart;
00097 
00098         /**
00099          * Restarts the spawn server.
00100          *
00101          * @pre System call interruption is disabled.
00102          * @throws SystemException An error occured while trying to setup the spawn server.
00103          * @throws IOException The specified log file could not be opened.
00104          */
00105         void restartServer() {
00106                 if (pid != 0) {
00107                         channel.close();
00108                         
00109                         // Wait at most 5 seconds for the spawn server to exit.
00110                         // If that doesn't work, kill it, then wait at most 5 seconds
00111                         // for it to exit.
00112                         time_t begin = InterruptableCalls::time(NULL);
00113                         bool done = false;
00114                         while (!done && InterruptableCalls::time(NULL) - begin < 5) {
00115                                 if (InterruptableCalls::waitpid(pid, NULL, WNOHANG) > 0) {
00116                                         done = true;
00117                                 } else {
00118                                         InterruptableCalls::usleep(100000);
00119                                 }
00120                         }
00121                         if (!done) {
00122                                 P_TRACE(2, "Spawn server did not exit in time, killing it...");
00123                                 InterruptableCalls::kill(pid, SIGTERM);
00124                                 begin = InterruptableCalls::time(NULL);
00125                                 while (InterruptableCalls::time(NULL) - begin < 5) {
00126                                         if (InterruptableCalls::waitpid(pid, NULL, WNOHANG) > 0) {
00127                                                 break;
00128                                         } else {
00129                                                 InterruptableCalls::usleep(100000);
00130                                         }
00131                                 }
00132                                 P_TRACE(2, "Spawn server has exited.");
00133                         }
00134                         pid = 0;
00135                 }
00136                 
00137                 int fds[2];
00138                 FILE *logFileHandle = NULL;
00139                 
00140                 serverNeedsRestart = true;
00141                 if (InterruptableCalls::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
00142                         throw SystemException("Cannot create a Unix socket", errno);
00143                 }
00144                 if (!logFile.empty()) {
00145                         logFileHandle = InterruptableCalls::fopen(logFile.c_str(), "a");
00146                         if (logFileHandle == NULL) {
00147                                 string message("Cannot open log file '");
00148                                 message.append(logFile);
00149                                 message.append("' for writing.");
00150                                 throw IOException(message);
00151                         }
00152                 }
00153 
00154                 pid = InterruptableCalls::fork();
00155                 if (pid == 0) {
00156                         if (!logFile.empty()) {
00157                                 dup2(fileno(logFileHandle), STDERR_FILENO);
00158                                 fclose(logFileHandle);
00159                         }
00160                         dup2(STDERR_FILENO, STDOUT_FILENO);
00161                         dup2(fds[1], SPAWN_SERVER_INPUT_FD);
00162                         
00163                         // Close all unnecessary file descriptors
00164                         for (long i = sysconf(_SC_OPEN_MAX) - 1; i > SPAWN_SERVER_INPUT_FD; i--) {
00165                                 close(i);
00166                         }
00167                         
00168                         if (!user.empty()) {
00169                                 struct passwd *entry = getpwnam(user.c_str());
00170                                 if (entry != NULL) {
00171                                         if (setgid(entry->pw_gid) != 0) {
00172                                                 int e = errno;
00173                                                 fprintf(stderr, "*** Passenger: cannot run spawn "
00174                                                         "manager as group %d: %s (%d)\n",
00175                                                         entry->pw_gid,
00176                                                         strerror(e),
00177                                                         e);
00178                                         }
00179                                         if (setuid(entry->pw_uid) != 0) {
00180                                                 int e = errno;
00181                                                 fprintf(stderr, "*** Passenger: cannot run spawn "
00182                                                         "manager as user %s (%d): %s (%d)\n",
00183                                                         user.c_str(), entry->pw_uid,
00184                                                         strerror(e),
00185                                                         e);
00186                                         }
00187                                 } else {
00188                                         fprintf(stderr, "*** Passenger: cannot run spawn manager "
00189                                                 "as nonexistant user '%s'.\n",
00190                                                 user.c_str());
00191                                 }
00192                                 fflush(stderr);
00193                         }
00194                         
00195                         execlp(rubyCommand.c_str(),
00196                                 rubyCommand.c_str(),
00197                                 spawnServerCommand.c_str(),
00198                                 // The spawn server changes the process names of the subservers
00199                                 // that it starts, for better usability. However, the process name length
00200                                 // (as shown by ps) is limited. Here, we try to expand that limit by
00201                                 // deliberately passing a useless whitespace string to the spawn server.
00202                                 // This argument is ignored by the spawn server. This works on some
00203                                 // systems, such as Ubuntu Linux.
00204                                 "                                                             ",
00205                                 NULL);
00206                         int e = errno;
00207                         fprintf(stderr, "*** Passenger ERROR: Could not start the spawn server: %s: %s (%d)\n",
00208                                 rubyCommand.c_str(), strerror(e), e);
00209                         fflush(stderr);
00210                         _exit(1);
00211                 } else if (pid == -1) {
00212                         int e = errno;
00213                         InterruptableCalls::close(fds[0]);
00214                         InterruptableCalls::close(fds[1]);
00215                         if (logFileHandle != NULL) {
00216                                 InterruptableCalls::fclose(logFileHandle);
00217                         }
00218                         pid = 0;
00219                         throw SystemException("Unable to fork a process", e);
00220                 } else {
00221                         InterruptableCalls::close(fds[1]);
00222                         if (!logFile.empty()) {
00223                                 InterruptableCalls::fclose(logFileHandle);
00224                         }
00225                         channel = MessageChannel(fds[0]);
00226                         serverNeedsRestart = false;
00227                         
00228                         #ifdef TESTING_SPAWN_MANAGER
00229                                 if (nextRestartShouldFail) {
00230                                         InterruptableCalls::kill(pid, SIGTERM);
00231                                         InterruptableCalls::usleep(500000);
00232                                 }
00233                         #endif
00234                 }
00235         }
00236         
00237         /**
00238          * Send the spawn command to the spawn server.
00239          *
00240          * @param appRoot The application root of the application to spawn.
00241          * @param lowerPrivilege Whether to lower the application's privileges.
00242          * @param lowestUser The user to fallback to if lowering privilege fails.
00243          * @param environment The RAILS_ENV/RACK_ENV environment that should be used.
00244          * @param spawnMethod The spawn method to use.
00245          * @param appType The application type.
00246          * @return An Application smart pointer, representing the spawned application.
00247          * @throws SpawnException Something went wrong.
00248          */
00249         ApplicationPtr sendSpawnCommand(
00250                 const string &appRoot,
00251                 bool lowerPrivilege,
00252                 const string &lowestUser,
00253                 const string &environment,
00254                 const string &spawnMethod,
00255                 const string &appType
00256         ) {
00257                 vector<string> args;
00258                 int ownerPipe;
00259                 
00260                 try {
00261                         channel.write("spawn_application",
00262                                 appRoot.c_str(),
00263                                 (lowerPrivilege) ? "true" : "false",
00264                                 lowestUser.c_str(),
00265                                 environment.c_str(),
00266                                 spawnMethod.c_str(),
00267                                 appType.c_str(),
00268                                 NULL);
00269                 } catch (const SystemException &e) {
00270                         throw SpawnException(string("Could not write 'spawn_application' "
00271                                 "command to the spawn server: ") + e.sys());
00272                 }
00273                 
00274                 try {
00275                         // Read status.
00276                         if (!channel.read(args)) {
00277                                 throw SpawnException("The spawn server has exited unexpectedly.");
00278                         }
00279                         if (args.size() != 1) {
00280                                 throw SpawnException("The spawn server sent an invalid message.");
00281                         }
00282                         if (args[0] == "error_page") {
00283                                 string errorPage;
00284                                 
00285                                 if (!channel.readScalar(errorPage)) {
00286                                         throw SpawnException("The spawn server has exited unexpectedly.");
00287                                 }
00288                                 throw SpawnException("An error occured while spawning the application.",
00289                                         errorPage);
00290                         } else if (args[0] != "ok") {
00291                                 throw SpawnException("The spawn server sent an invalid message.");
00292                         }
00293                         
00294                         // Read application info.
00295                         if (!channel.read(args)) {
00296                                 throw SpawnException("The spawn server has exited unexpectedly.");
00297                         }
00298                 } catch (const SystemException &e) {
00299                         throw SpawnException(string("Could not read from the spawn server: ") + e.sys());
00300                 }
00301                 
00302                 try {
00303                         ownerPipe = channel.readFileDescriptor();
00304                 } catch (const SystemException &e) {
00305                         throw SpawnException(string("Could not receive the spawned "
00306                                 "application's owner pipe from the spawn server: ") +
00307                                 e.sys());
00308                 } catch (const IOException &e) {
00309                         throw SpawnException(string("Could not receive the spawned "
00310                                 "application's owner pipe from the spawn server: ") +
00311                                 e.what());
00312                 }
00313                 
00314                 if (args.size() != 3) {
00315                         InterruptableCalls::close(ownerPipe);
00316                         throw SpawnException("The spawn server sent an invalid message.");
00317                 }
00318                 
00319                 pid_t pid = atoi(args[0]);
00320                 bool usingAbstractNamespace = args[2] == "true";
00321                 
00322                 if (!usingAbstractNamespace) {
00323                         int ret;
00324                         do {
00325                                 ret = chmod(args[1].c_str(), S_IRUSR | S_IWUSR);
00326                         } while (ret == -1 && errno == EINTR);
00327                         do {
00328                                 ret = chown(args[1].c_str(), getuid(), getgid());
00329                         } while (ret == -1 && errno == EINTR);
00330                 }
00331                 return ApplicationPtr(new Application(appRoot, pid, args[1],
00332                         usingAbstractNamespace, ownerPipe));
00333         }
00334         
00335         /**
00336          * @throws boost::thread_interrupted
00337          */
00338         ApplicationPtr
00339         handleSpawnException(const SpawnException &e, const string &appRoot,
00340                              bool lowerPrivilege, const string &lowestUser,
00341                              const string &environment, const string &spawnMethod,
00342                              const string &appType) {
00343                 bool restarted;
00344                 try {
00345                         P_DEBUG("Spawn server died. Attempting to restart it...");
00346                         this_thread::disable_syscall_interruption dsi;
00347                         restartServer();
00348                         P_DEBUG("Restart seems to be successful.");
00349                         restarted = true;
00350                 } catch (const IOException &e) {
00351                         P_DEBUG("Restart failed: " << e.what());
00352                         restarted = false;
00353                 } catch (const SystemException &e) {
00354                         P_DEBUG("Restart failed: " << e.what());
00355                         restarted = false;
00356                 }
00357                 if (restarted) {
00358                         return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser,
00359                                 environment, spawnMethod, appType);
00360                 } else {
00361                         throw SpawnException("The spawn server died unexpectedly, and restarting it failed.");
00362                 }
00363         }
00364         
00365         /**
00366          * Send the reload command to the spawn server.
00367          *
00368          * @param appRoot The application root to reload.
00369          * @throws SystemException Something went wrong.
00370          */
00371         void sendReloadCommand(const string &appRoot) {
00372                 try {
00373                         channel.write("reload", appRoot.c_str(), NULL);
00374                 } catch (const SystemException &e) {
00375                         throw SystemException("Could not write 'reload' command "
00376                                 "to the spawn server", e.code());
00377                 }
00378         }
00379         
00380         void handleReloadException(const SystemException &e, const string &appRoot) {
00381                 bool restarted;
00382                 try {
00383                         P_DEBUG("Spawn server died. Attempting to restart it...");
00384                         restartServer();
00385                         P_DEBUG("Restart seems to be successful.");
00386                         restarted = true;
00387                 } catch (const IOException &e) {
00388                         P_DEBUG("Restart failed: " << e.what());
00389                         restarted = false;
00390                 } catch (const SystemException &e) {
00391                         P_DEBUG("Restart failed: " << e.what());
00392                         restarted = false;
00393                 }
00394                 if (restarted) {
00395                         return sendReloadCommand(appRoot);
00396                 } else {
00397                         throw SpawnException("The spawn server died unexpectedly, and restarting it failed.");
00398                 }
00399         }
00400         
00401         IOException prependMessageToException(const IOException &e, const string &message) {
00402                 return IOException(message + ": " + e.what());
00403         }
00404         
00405         SystemException prependMessageToException(const SystemException &e, const string &message) {
00406                 return SystemException(message + ": " + e.brief(), e.code());
00407         }
00408 
00409 public:
00410         #ifdef TESTING_SPAWN_MANAGER
00411                 bool nextRestartShouldFail;
00412         #endif
00413 
00414         /**
00415          * Construct a new SpawnManager.
00416          *
00417          * @param spawnServerCommand The filename of the spawn server to use.
00418          * @param logFile Specify a log file that the spawn server should use.
00419          *            Messages on its standard output and standard error channels
00420          *            will be written to this log file. If an empty string is
00421          *            specified, no log file will be used, and the spawn server
00422          *            will use the same standard output/error channels as the
00423          *            current process.
00424          * @param rubyCommand The Ruby interpreter's command.
00425          * @param user The user that the spawn manager should run as. This
00426          *             parameter only has effect if the current process is
00427          *             running as root. If the empty string is given, or if
00428          *             the <tt>user</tt> is not a valid username, then
00429          *             the spawn manager will be run as the current user.
00430          * @throws SystemException An error occured while trying to setup the spawn server.
00431          * @throws IOException The specified log file could not be opened.
00432          */
00433         SpawnManager(const string &spawnServerCommand,
00434                      const string &logFile = "",
00435                      const string &rubyCommand = "ruby",
00436                      const string &user = "") {
00437                 this->spawnServerCommand = spawnServerCommand;
00438                 this->logFile = logFile;
00439                 this->rubyCommand = rubyCommand;
00440                 this->user = user;
00441                 pid = 0;
00442                 #ifdef TESTING_SPAWN_MANAGER
00443                         nextRestartShouldFail = false;
00444                 #endif
00445                 this_thread::disable_interruption di;
00446                 this_thread::disable_syscall_interruption dsi;
00447                 try {
00448                         restartServer();
00449                 } catch (const IOException &e) {
00450                         throw prependMessageToException(e, "Could not start the spawn server");
00451                 } catch (const SystemException &e) {
00452                         throw prependMessageToException(e, "Could not start the spawn server");
00453                 }
00454         }
00455         
00456         ~SpawnManager() throw() {
00457                 if (pid != 0) {
00458                         this_thread::disable_interruption di;
00459                         this_thread::disable_syscall_interruption dsi;
00460                         P_TRACE(2, "Shutting down spawn manager (PID " << pid << ").");
00461                         channel.close();
00462                         InterruptableCalls::waitpid(pid, NULL, 0);
00463                         P_TRACE(2, "Spawn manager exited.");
00464                 }
00465         }
00466         
00467         /**
00468          * Spawn a new instance of a Ruby on Rails or Rack application.
00469          *
00470          * If the spawn server died during the spawning process, then the server
00471          * will be automatically restarted, and another spawn attempt will be made.
00472          * If restarting the server fails, or if the second spawn attempt fails,
00473          * then an exception will be thrown.
00474          *
00475          * If <tt>lowerPrivilege</tt> is true, then it will be attempt to
00476          * switch the spawned application instance to the user who owns the
00477          * application's <tt>config/environment.rb</tt>, and to the default
00478          * group of that user.
00479          *
00480          * If that user doesn't exist on the system, or if that user is root,
00481          * then it will be attempted to switch to the username given by
00482          * <tt>lowestUser</tt> (and to the default group of that user).
00483          * If <tt>lowestUser</tt> doesn't exist either, or if switching user failed
00484          * (because the spawn server process does not have the privilege to do so),
00485          * then the application will be spawned anyway, without reporting an error.
00486          *
00487          * It goes without saying that lowering privilege is only possible if
00488          * the spawn server is running as root (and thus, by induction, that
00489          * Passenger and Apache's control process are also running as root).
00490          * Note that if Apache is listening on port 80, then its control process must
00491          * be running as root. See "doc/Security of user switching.txt" for
00492          * a detailed explanation.
00493          *
00494          * @param appRoot The application root of a RoR application, i.e. the folder that
00495          *             contains 'app/', 'public/', 'config/', etc. This must be a valid directory,
00496          *             but the path does not have to be absolute.
00497          * @param lowerPrivilege Whether to lower the application's privileges.
00498          * @param lowestUser The user to fallback to if lowering privilege fails.
00499          * @param environment The RAILS_ENV/RACK_ENV environment that should be used. May not be empty.
00500          * @param spawnMethod The spawn method to use. Either "smart" or "conservative".
00501          *                    See the Ruby class SpawnManager for details.
00502          * @param appType The application type. Either "rails" or "rack".
00503          * @return A smart pointer to an Application object, which represents the application
00504          *         instance that has been spawned. Use this object to communicate with the
00505          *         spawned application.
00506          * @throws SpawnException Something went wrong.
00507          * @throws boost::thread_interrupted
00508          */
00509         ApplicationPtr spawn(
00510                 const string &appRoot,
00511                 bool lowerPrivilege = true,
00512                 const string &lowestUser = "nobody",
00513                 const string &environment = "production",
00514                 const string &spawnMethod = "smart",
00515                 const string &appType = "rails"
00516         ) {
00517                 mutex::scoped_lock l(lock);
00518                 try {
00519                         return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser,
00520                                 environment, spawnMethod, appType);
00521                 } catch (const SpawnException &e) {
00522                         if (e.hasErrorPage()) {
00523                                 throw;
00524                         } else {
00525                                 return handleSpawnException(e, appRoot, lowerPrivilege,
00526                                         lowestUser, environment, spawnMethod, appType);
00527                         }
00528                 }
00529         }
00530         
00531         /**
00532          * Remove the cached application instances at the given application root.
00533          *
00534          * Application code might be cached in memory. But once it a while, it will
00535          * be necessary to reload the code for an application, such as after
00536          * deploying a new version of the application. This method makes sure that
00537          * any cached application code is removed, so that the next time an
00538          * application instance is spawned, the application code will be freshly
00539          * loaded into memory.
00540          *
00541          * @throws SystemException Unable to communicate with the spawn server,
00542          *         even after a restart.
00543          * @throws SpawnException The spawn server died unexpectedly, and a
00544          *         restart was attempted, but it failed.
00545          */
00546         void reload(const string &appRoot) {
00547                 this_thread::disable_interruption di;
00548                 this_thread::disable_syscall_interruption dsi;
00549                 try {
00550                         return sendReloadCommand(appRoot);
00551                 } catch (const SystemException &e) {
00552                         return handleReloadException(e, appRoot);
00553                 }
00554         }
00555         
00556         /**
00557          * Get the Process ID of the spawn server. This method is used in the unit tests
00558          * and should not be used directly.
00559          */
00560         pid_t getServerPid() const {
00561                 return pid;
00562         }
00563 };
00564 
00565 /** Convenient alias for SpawnManager smart pointer. */
00566 typedef shared_ptr<SpawnManager> SpawnManagerPtr;
00567 
00568 } // namespace Passenger
00569 
00570 #endif /* _PASSENGER_SPAWN_MANAGER_H_ */

Generated on Fri Jan 23 08:28:57 2009 for Passenger by  doxygen 1.4.7