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);
}