0
#include <boost/shared_ptr.hpp>
0
+#include <boost/thread/mutex.hpp>
0
#include "Application.h"
0
#include "MessageChannel.h"
0
#include "Exceptions.h"
0
@@ -26,12 +28,17 @@ using namespace boost;
0
* This class is responsible for spawning new instances of Ruby on Rails applications.
0
* Use the spawn() method to do so.
0
+ * This class is fully thread-safe.
0
* <h2>Implementation details</h2>
0
* Internally, it makes use of a spawn server, which is written in Ruby. This server
0
* is automatically started when a SpawnManager instance is created, and automatically
0
* shutdown when that instance is destroyed. Spawning requests are sent to the server,
0
* and details about the spawned process is returned.
0
+ * If the spawn server dies during the middle of an operation, it will be restarted.
0
+ * See spawn() for full details.
0
* The communication channel with the server is anonymous, i.e. no other processes
0
* can access the communication channel, so communication is guaranteed to be safe
0
* (unless, of course, if the spawn server itself is a trojan).
0
@@ -47,34 +54,35 @@ using namespace boost;
0
+ string spawnServerCommand;
0
MessageChannel channel;
0
+ bool serverNeedsRestart;
0
- * Construct a new SpawnManager.
0
+ * Restarts the spawn server.
0
- * @param spawnServerCommand The filename of the spawn server to use.
0
- * @param logFile Specify a log file that the spawn manager should use.
0
- * Messages on its standard output and standard error channels
0
- * will be written to this log file. If an empty string is
0
- * specified, no log file will be used, and the spawn server
0
- * will use the same standard output/error channels as the
0
- * @param environment The RAILS_ENV environment that all RoR applications
0
- * should use. If an empty string is specified, the current value
0
- * of the RAILS_ENV environment variable will be used.
0
- * @param rubyCommand The Ruby interpreter's command.
0
- * @param SystemException An error occured while trying to setup the spawn server.
0
+ * @throws SystemException An error occured while trying to setup the spawn server.
0
* @throws IOException The specified log file could not be opened.
0
- SpawnManager(const string &spawnServerCommand,
0
- const string &logFile = "",
0
- const string &environment = "production",
0
- const string &rubyCommand = "ruby") {
0
+ void restartServer() {
0
+ // TODO: should not wait infinitely
0
+ waitpid(pid, NULL, 0);
0
FILE *logFileHandle = NULL;
0
+ serverNeedsRestart = true;
0
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
0
throw SystemException("Cannot create a Unix socket", errno);
0
@@ -90,68 +98,160 @@ public:
0
- if (!logFile.empty()) {
0
- dup2(fileno(logFileHandle), STDERR_FILENO);
0
- fclose(logFileHandle);
0
- dup2(STDERR_FILENO, STDOUT_FILENO);
0
- if (!environment.empty()) {
0
- setenv("RAILS_ENV", environment.c_str(), true);
0
- dup2(fds[1], STDIN_FILENO);
0
- // Close all other file descriptors
0
- for (int i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
0
- if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) {
0
+ if (!logFile.empty()) {
0
+ dup2(fileno(logFileHandle), STDERR_FILENO);
0
+ fclose(logFileHandle);
0
+ dup2(STDERR_FILENO, STDOUT_FILENO);
0
+ if (!environment.empty()) {
0
+ setenv("RAILS_ENV", environment.c_str(), true);
0
+ dup2(fds[1], STDIN_FILENO);
0
+ // Close all other file descriptors
0
+ for (int i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
0
+ if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) {
0
- execlp(rubyCommand.c_str(), rubyCommand.c_str(), spawnServerCommand.c_str(), NULL);
0
- fprintf(stderr, "Unable to run %s: %s\n", rubyCommand.c_str(), strerror(errno));
0
- } else if (pid == -1) {
0
- fprintf(stderr, "Unable to fork a process: %s\n", strerror(errno));
0
+ execlp(rubyCommand.c_str(), rubyCommand.c_str(), spawnServerCommand.c_str(), NULL);
0
+ fprintf(stderr, "Unable to run %s: %s\n", rubyCommand.c_str(), strerror(errno));
0
} else if (pid == -1) {
0
if (logFileHandle != NULL) {
0
throw SystemException("Unable to fork a process", errno);
0
if (!logFile.empty()) {
0
- waitpid(pid, NULL, 0);
0
channel = MessageChannel(fds[0]);
0
+ serverNeedsRestart = false;
0
+ * Thrown when SpawnManager tried to restart the spawn server, but failed.
0
+ * Use getSubException() to find out more details about the failure.
0
+ class RestartException: public exception {
0
+ shared_ptr<exception> m_subexception;
0
+ RestartException(shared_ptr<exception> subexception)
0
+ : m_subexception(subexception) {}
0
+ virtual ~RestartException() throw() {}
0
+ virtual const char *what() const throw() {
0
+ return m_subexception->what();
0
+ * An exception which contains more details about why the restart failed.
0
+ shared_ptr<exception> getSubException() const throw() {
0
+ return m_subexception;
0
+ * Construct a new SpawnManager.
0
+ * @param spawnServerCommand The filename of the spawn server to use.
0
+ * @param logFile Specify a log file that the spawn manager should use.
0
+ * Messages on its standard output and standard error channels
0
+ * will be written to this log file. If an empty string is
0
+ * specified, no log file will be used, and the spawn server
0
+ * will use the same standard output/error channels as the
0
+ * @param environment The RAILS_ENV environment that all RoR applications
0
+ * should use. If an empty string is specified, the current value
0
+ * of the RAILS_ENV environment variable will be used.
0
+ * @param rubyCommand The Ruby interpreter's command.
0
+ * @throws SystemException An error occured while trying to setup the spawn server.
0
+ * @throws IOException The specified log file could not be opened.
0
+ SpawnManager(const string &spawnServerCommand,
0
+ const string &logFile = "",
0
+ const string &environment = "production",
0
+ const string &rubyCommand = "ruby") {
0
+ this->spawnServerCommand = spawnServerCommand;
0
+ this->logFile = logFile;
0
+ this->environment = environment;
0
+ this->rubyCommand = rubyCommand;
0
~SpawnManager() throw() {
0
+ waitpid(pid, NULL, 0);
0
- ApplicationPtr spawn(const string &appRoot, const string &username = "") {
0
+ * Spawn a new instance of a Ruby on Rails application.
0
+ * If something went wrong in the server during the spawning process,
0
+ * then an IOException or a SystemException will be thrown. The server will
0
+ * be restarted next time spawn() is called. If the server crashes during
0
+ * the restart, a RestartException will be thrown.
0
+ * @param appRoot The application root of a RoR application, i.e. the folder that
0
+ * contains 'app/', 'public/', 'config/', etc. This must be a valid directory,
0
+ * but the path does not have to be absolute.
0
+ * @param user The user
0
+ * @return A smart pointer to an Application object, which represents the application
0
+ * instance that has been spawned. Use this object to communicate with the
0
+ * spawned application.
0
+ * @throws IOException Something went wrong during spawning.
0
+ * @throws SystemException Something went wrong during spawning.
0
+ * @throws RestartException An attempt to restart the spawn server was made, but that failed.
0
+ ApplicationPtr spawn(const string &appRoot, const string &user = "", const string &group = "") {
0
+ mutex::scoped_lock l(lock);
0
+ if (serverNeedsRestart) {
0
+ P_TRACE("Restarting spawn server.");
0
+ } catch (const IOException &e) {
0
+ P_TRACE("Failed to restart spawn server: " << e.what());
0
+ shared_ptr<IOException> copy(new IOException(e));
0
+ throw RestartException(copy);
0
+ } catch (const SystemException &e) {
0
+ P_TRACE("Failed to restart spawn server: " << e.what());
0
+ shared_ptr<SystemException> copy(new SystemException(e));
0
+ throw RestartException(copy);
0
- channel.write("spawn_application", appRoot.c_str(), username.c_str(), NULL);
0
- if (!channel.read(args)) {
0
- throw IOException("The spawn manager server has exited unexpectedly.");
0
+ channel.write("spawn_application", appRoot.c_str(), user.c_str(), group.c_str(), NULL);
0
+ if (!channel.read(args)) {
0
+ throw IOException("The spawn server has exited unexpectedly.");
0
+ pid_t pid = atoi(args.front().c_str());
0
+ int reader = channel.readFileDescriptor();
0
+ int writer = channel.readFileDescriptor();
0
+ return ApplicationPtr(new Application(appRoot, pid, reader, writer));
0
+ } catch (const exception &e) {
0
+ P_TRACE("Spawn server died. Will restart it next time.");
0
+ serverNeedsRestart = true;
0
- pid_t pid = atoi(args.front().c_str());
0
- int reader = channel.readFileDescriptor();
0
- int writer = channel.readFileDescriptor();
0
- return ApplicationPtr(new Application(appRoot, pid, reader, writer));
Comments
No one has commented yet.