ext/apache2/SpawnManager.h in passenger-1.0.5 vs ext/apache2/SpawnManager.h in passenger-2.0.1

- old
+ new

@@ -1,9 +1,11 @@ /* * Phusion Passenger - http://www.modrails.com/ * Copyright (C) 2008 Phusion * + * Phusion Passenger is a trademark of Hongli Lai & Ninh Bui. + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, @@ -19,40 +21,39 @@ #define _PASSENGER_SPAWN_MANAGER_H_ #include <string> #include <list> #include <boost/shared_ptr.hpp> -#include <boost/thread/mutex.hpp> +#include <boost/thread.hpp> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <arpa/inet.h> #include <cstdio> #include <cstdarg> #include <unistd.h> #include <errno.h> #include <pwd.h> -#ifdef TESTING_SPAWN_MANAGER - #include <signal.h> -#endif +#include <signal.h> #include "Application.h" #include "MessageChannel.h" #include "Exceptions.h" #include "Logging.h" +#include "System.h" namespace Passenger { using namespace std; using namespace boost; /** - * @brief Spawning of Ruby on Rails application instances. + * @brief Spawning of Ruby on Rails/Rack application instances. * - * This class is responsible for spawning new instances of Ruby on Rails applications. - * Use the spawn() method to do so. + * This class is responsible for spawning new instances of Ruby on Rails or + * Rack applications. Use the spawn() method to do so. * * @note This class is fully thread-safe. * * <h2>Implementation details</h2> * Internally, this class makes use of a spawn server, which is written in Ruby. This server @@ -83,11 +84,10 @@ private: static const int SPAWN_SERVER_INPUT_FD = 3; string spawnServerCommand; string logFile; - string environment; string rubyCommand; string user; mutex lock; @@ -96,38 +96,39 @@ bool serverNeedsRestart; /** * Restarts the spawn server. * + * @pre System call interruption is disabled. * @throws SystemException An error occured while trying to setup the spawn server. * @throws IOException The specified log file could not be opened. */ void restartServer() { if (pid != 0) { channel.close(); // Wait at most 5 seconds for the spawn server to exit. // If that doesn't work, kill it, then wait at most 5 seconds // for it to exit. - time_t begin = time(NULL); + time_t begin = InterruptableCalls::time(NULL); bool done = false; - while (!done && time(NULL) - begin < 5) { - if (waitpid(pid, NULL, WNOHANG) > 0) { + while (!done && InterruptableCalls::time(NULL) - begin < 5) { + if (InterruptableCalls::waitpid(pid, NULL, WNOHANG) > 0) { done = true; } else { - usleep(100000); + InterruptableCalls::usleep(100000); } } if (!done) { P_TRACE(2, "Spawn server did not exit in time, killing it..."); - kill(pid, SIGTERM); - begin = time(NULL); - while (time(NULL) - begin < 5) { - if (waitpid(pid, NULL, WNOHANG) > 0) { + InterruptableCalls::kill(pid, SIGTERM); + begin = InterruptableCalls::time(NULL); + while (InterruptableCalls::time(NULL) - begin < 5) { + if (InterruptableCalls::waitpid(pid, NULL, WNOHANG) > 0) { break; } else { - usleep(100000); + InterruptableCalls::usleep(100000); } } P_TRACE(2, "Spawn server has exited."); } pid = 0; @@ -135,40 +136,35 @@ int fds[2]; FILE *logFileHandle = NULL; serverNeedsRestart = true; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) { + if (InterruptableCalls::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) { throw SystemException("Cannot create a Unix socket", errno); } if (!logFile.empty()) { - logFileHandle = fopen(logFile.c_str(), "a"); + logFileHandle = InterruptableCalls::fopen(logFile.c_str(), "a"); if (logFileHandle == NULL) { string message("Cannot open log file '"); message.append(logFile); message.append("' for writing."); throw IOException(message); } } - pid = fork(); + pid = InterruptableCalls::fork(); if (pid == 0) { if (!logFile.empty()) { dup2(fileno(logFileHandle), STDERR_FILENO); fclose(logFileHandle); } dup2(STDERR_FILENO, STDOUT_FILENO); - if (!environment.empty()) { - setenv("RAILS_ENV", environment.c_str(), true); - } dup2(fds[1], SPAWN_SERVER_INPUT_FD); // Close all unnecessary file descriptors - for (long i = sysconf(_SC_OPEN_MAX) - 1; i >= 0; i--) { - if (i > SPAWN_SERVER_INPUT_FD) { - close(i); - } + for (long i = sysconf(_SC_OPEN_MAX) - 1; i > SPAWN_SERVER_INPUT_FD; i--) { + close(i); } if (!user.empty()) { struct passwd *entry = getpwnam(user.c_str()); if (entry != NULL) { @@ -206,35 +202,35 @@ // This argument is ignored by the spawn server. This works on some // systems, such as Ubuntu Linux. " ", NULL); int e = errno; - fprintf(stderr, "*** Passenger ERROR: Could not start the spawn server: %s: %s\n", - rubyCommand.c_str(), strerror(e)); + fprintf(stderr, "*** Passenger ERROR: Could not start the spawn server: %s: %s (%d)\n", + rubyCommand.c_str(), strerror(e), e); fflush(stderr); _exit(1); } else if (pid == -1) { int e = errno; - close(fds[0]); - close(fds[1]); + InterruptableCalls::close(fds[0]); + InterruptableCalls::close(fds[1]); if (logFileHandle != NULL) { - fclose(logFileHandle); + InterruptableCalls::fclose(logFileHandle); } pid = 0; throw SystemException("Unable to fork a process", e); } else { - close(fds[1]); + InterruptableCalls::close(fds[1]); if (!logFile.empty()) { - fclose(logFileHandle); + InterruptableCalls::fclose(logFileHandle); } channel = MessageChannel(fds[0]); serverNeedsRestart = false; #ifdef TESTING_SPAWN_MANAGER if (nextRestartShouldFail) { - kill(pid, SIGTERM); - usleep(500000); + InterruptableCalls::kill(pid, SIGTERM); + InterruptableCalls::usleep(500000); } #endif } } @@ -242,22 +238,35 @@ * Send the spawn command to the spawn server. * * @param appRoot The application root of the application to spawn. * @param lowerPrivilege Whether to lower the application's privileges. * @param lowestUser The user to fallback to if lowering privilege fails. + * @param environment The RAILS_ENV/RACK_ENV environment that should be used. + * @param spawnMethod The spawn method to use. + * @param appType The application type. * @return An Application smart pointer, representing the spawned application. * @throws SpawnException Something went wrong. */ - ApplicationPtr sendSpawnCommand(const string &appRoot, bool lowerPrivilege, const string &lowestUser) { + ApplicationPtr sendSpawnCommand( + const string &appRoot, + bool lowerPrivilege, + const string &lowestUser, + const string &environment, + const string &spawnMethod, + const string &appType + ) { vector<string> args; int ownerPipe; try { channel.write("spawn_application", appRoot.c_str(), (lowerPrivilege) ? "true" : "false", lowestUser.c_str(), + environment.c_str(), + spawnMethod.c_str(), + appType.c_str(), NULL); } catch (const SystemException &e) { throw SpawnException(string("Could not write 'spawn_application' " "command to the spawn server: ") + e.sys()); } @@ -301,31 +310,42 @@ "application's owner pipe from the spawn server: ") + e.what()); } if (args.size() != 3) { - close(ownerPipe); + InterruptableCalls::close(ownerPipe); throw SpawnException("The spawn server sent an invalid message."); } pid_t pid = atoi(args[0]); bool usingAbstractNamespace = args[2] == "true"; if (!usingAbstractNamespace) { - chmod(args[1].c_str(), S_IRUSR | S_IWUSR); - chown(args[1].c_str(), getuid(), getgid()); + int ret; + do { + ret = chmod(args[1].c_str(), S_IRUSR | S_IWUSR); + } while (ret == -1 && errno == EINTR); + do { + ret = chown(args[1].c_str(), getuid(), getgid()); + } while (ret == -1 && errno == EINTR); } return ApplicationPtr(new Application(appRoot, pid, args[1], usingAbstractNamespace, ownerPipe)); } + /** + * @throws boost::thread_interrupted + */ ApplicationPtr handleSpawnException(const SpawnException &e, const string &appRoot, - bool lowerPrivilege, const string &lowestUser) { + bool lowerPrivilege, const string &lowestUser, + const string &environment, const string &spawnMethod, + const string &appType) { bool restarted; try { P_DEBUG("Spawn server died. Attempting to restart it..."); + this_thread::disable_syscall_interruption dsi; restartServer(); P_DEBUG("Restart seems to be successful."); restarted = true; } catch (const IOException &e) { P_DEBUG("Restart failed: " << e.what()); @@ -333,11 +353,12 @@ } catch (const SystemException &e) { P_DEBUG("Restart failed: " << e.what()); restarted = false; } if (restarted) { - return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser); + return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser, + environment, spawnMethod, appType); } else { throw SpawnException("The spawn server died unexpectedly, and restarting it failed."); } } @@ -398,13 +419,10 @@ * Messages on its standard output and standard error channels * will be written to this log file. If an empty string is * specified, no log file will be used, and the spawn server * will use the same standard output/error channels as the * current process. - * @param environment The RAILS_ENV environment that all RoR applications - * should use. If an empty string is specified, the current value - * of the RAILS_ENV environment variable will be used. * @param rubyCommand The Ruby interpreter's command. * @param user The user that the spawn manager should run as. This * parameter only has effect if the current process is * running as root. If the empty string is given, or if * the <tt>user</tt> is not a valid username, then @@ -412,22 +430,22 @@ * @throws SystemException An error occured while trying to setup the spawn server. * @throws IOException The specified log file could not be opened. */ SpawnManager(const string &spawnServerCommand, const string &logFile = "", - const string &environment = "production", const string &rubyCommand = "ruby", const string &user = "") { this->spawnServerCommand = spawnServerCommand; this->logFile = logFile; - this->environment = environment; this->rubyCommand = rubyCommand; this->user = user; pid = 0; #ifdef TESTING_SPAWN_MANAGER nextRestartShouldFail = false; #endif + this_thread::disable_interruption di; + this_thread::disable_syscall_interruption dsi; try { restartServer(); } catch (const IOException &e) { throw prependMessageToException(e, "Could not start the spawn server"); } catch (const SystemException &e) { @@ -435,17 +453,21 @@ } } ~SpawnManager() throw() { if (pid != 0) { + this_thread::disable_interruption di; + this_thread::disable_syscall_interruption dsi; + P_TRACE(2, "Shutting down spawn manager (PID " << pid << ")."); channel.close(); - waitpid(pid, NULL, 0); + InterruptableCalls::waitpid(pid, NULL, 0); + P_TRACE(2, "Spawn manager exited."); } } /** - * Spawn a new instance of a Ruby on Rails application. + * Spawn a new instance of a Ruby on Rails or Rack application. * * If the spawn server died during the spawning process, then the server * will be automatically restarted, and another spawn attempt will be made. * If restarting the server fails, or if the second spawn attempt fails, * then an exception will be thrown. @@ -472,24 +494,38 @@ * @param appRoot The application root of a RoR application, i.e. the folder that * contains 'app/', 'public/', 'config/', etc. This must be a valid directory, * but the path does not have to be absolute. * @param lowerPrivilege Whether to lower the application's privileges. * @param lowestUser The user to fallback to if lowering privilege fails. + * @param environment The RAILS_ENV/RACK_ENV environment that should be used. May not be empty. + * @param spawnMethod The spawn method to use. Either "smart" or "conservative". + * See the Ruby class SpawnManager for details. + * @param appType The application type. Either "rails" or "rack". * @return A smart pointer to an Application object, which represents the application * instance that has been spawned. Use this object to communicate with the * spawned application. * @throws SpawnException Something went wrong. + * @throws boost::thread_interrupted */ - ApplicationPtr spawn(const string &appRoot, bool lowerPrivilege = true, const string &lowestUser = "nobody") { + ApplicationPtr spawn( + const string &appRoot, + bool lowerPrivilege = true, + const string &lowestUser = "nobody", + const string &environment = "production", + const string &spawnMethod = "smart", + const string &appType = "rails" + ) { mutex::scoped_lock l(lock); try { - return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser); + return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser, + environment, spawnMethod, appType); } catch (const SpawnException &e) { if (e.hasErrorPage()) { throw; } else { - return handleSpawnException(e, appRoot, lowerPrivilege, lowestUser); + return handleSpawnException(e, appRoot, lowerPrivilege, + lowestUser, environment, spawnMethod, appType); } } } /** @@ -506,9 +542,11 @@ * even after a restart. * @throws SpawnException The spawn server died unexpectedly, and a * restart was attempted, but it failed. */ void reload(const string &appRoot) { + this_thread::disable_interruption di; + this_thread::disable_syscall_interruption dsi; try { return sendReloadCommand(appRoot); } catch (const SystemException &e) { return handleReloadException(e, appRoot); }