- Main Page
- Modules
- Classes
- Files

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