Passenger: ApplicationPoolServer.h Source File

- Main Page
- Modules
- Classes
- Files

- File List

# ApplicationPoolServer.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_APPLICATION_POOL_SERVER_H_
    00021 #define _PASSENGER_APPLICATION_POOL_SERVER_H_
    00022
    00023 #include <boost/shared_ptr.hpp>
    00024 #include <boost/thread/mutex.hpp>
    00025 #include <oxt/system_calls.hpp>
    00026 #include <oxt/backtrace.hpp>
    00027
    00028 #include <sys/types.h>
    00029 #include <sys/stat.h>
    00030 #include <sys/wait.h>
    00031 #include <sys/socket.h>
    00032 #include <cstdio>
    00033 #include <cstdlib>
    00034 #include <limits.h>
    00035 #include <errno.h>
    00036 #include <unistd.h>
    00037 #include <signal.h>
    00038 #include <fcntl.h>
    00039
    00040 #include "MessageChannel.h"
    00041 #include "ApplicationPool.h"
    00042 #include "Application.h"
    00043 #include "Exceptions.h"
    00044 #include "Logging.h"
    00045
    00046 namespace Passenger {
    00047
    00048 using namespace std;
    00049 using namespace boost;
    00050 using namespace oxt;
    00051
    00052
    00053 /**
    00054  * Multi-process usage support for ApplicationPool.
    00055  *
    00056  * ApplicationPoolServer implements a client/server architecture for ApplicationPool.
    00057  * This allows one to use ApplicationPool in a multi-process environment (unlike
    00058  * StandardApplicationPool). The cache/pool data is stored in the server. Different
    00059  * processes can then access the pool through the server.
    00060  *
    00061  * ApplicationPoolServer itself does not inherit ApplicationPool. Instead, it returns
    00062  * an ApplicationPool object via the connect() call. For example:
    00063  * @code
    00064  *   // Create an ApplicationPoolServer.
    00065  *   ApplicationPoolServer server(...);
    00066  *
    00067  *   // Now fork a child process, like Apache's prefork MPM eventually will.
    00068  *   pid_t pid = fork();
    00069  *   if (pid == 0) {
    00070  *       // Child process
    00071  *
    00072  *       // Connect to the server. After connection, we have an ApplicationPool
    00073  *       // object!
    00074  *       ApplicationPoolPtr pool(server.connect());
    00075  *
    00076  *       // We don't need to connect to the server anymore, so we detach from it.
    00077  *       // This frees up some resources, such as file descriptors.
    00078  *       server.detach();
    00079  *
    00080  *       ApplicationPool::SessionPtr session(pool->get("/home/webapps/foo"));
    00081  *       do_something_with(session);
    00082  *
    00083  *       _exit(0);
    00084  *   } else {
    00085  *       // Parent process
    00086  *       waitpid(pid, NULL, 0);
    00087  *   }
    00088  * @endcode
    00089  *
    00090  * <h2>Implementation notes</h2>
    00091  *
    00092  * <h3>Separate server executable</h3>
    00093  * The actual server is implemented in ApplicationPoolServerExecutable.cpp, this class is
    00094  * just a convenience class for starting/stopping the server executable and connecting
    00095  * to it.
    00096  *
    00097  * In the past, the server logic itself was implemented in this class. This implies that
    00098  * the ApplicationPool server ran inside the Apache process. This presented us with several
    00099  * problems:
    00100  * - Because of the usage of threads in the ApplicationPool server, the Apache VM size would
    00101  *   go way up. This gave people the (wrong) impression that Passenger uses a lot of memory,
    00102  *   or that it leaks memory.
    00103  * - Although it's not entirely confirmed, we suspect that it caused heap fragmentation as
    00104  *   well. Apache allocates lots and lots of small objects on the heap, and ApplicationPool
    00105  *   server isn't exactly helping. This too gave people the (wrong) impression that
    00106  *   Passenger leaks memory.
    00107  * - It would unnecessarily bloat the VM size of Apache worker processes.
    00108  * - We had to resort to all kinds of tricks to make sure that fork()ing a process doesn't
    00109  *   result in file descriptor leaks.
    00110  * - Despite everything, there was still a small chance that file descriptor leaks would
    00111  *   occur, and this could not be fixed. The reason for this is that the Apache control
    00112  *   process may call fork() right after the ApplicationPool server has established a new
    00113  *   connection with a client.
    00114  *
    00115  * Because of these problems, it was decided to split the ApplicationPool server to a
    00116  * separate executable. This comes with no performance hit.
    00117  *
    00118  * <h3>Anonymous server socket</h3>
    00119  * Notice that ApplicationPoolServer does do not use TCP sockets at all, or even named Unix
    00120  * sockets, despite being a server that can handle multiple clients! So ApplicationPoolServer
    00121  * will expose no open ports or temporary Unix socket files. Only child processes are able
    00122  * to use the ApplicationPoolServer.
    00123  *
    00124  * This is implemented through anonymous Unix sockets (<tt>socketpair()</tt>) and file descriptor
    00125  * passing. It allows one to emulate <tt>accept()</tt>. ApplicationPoolServer is connected to
    00126  * the server executable through a Unix socket pair. connect() sends a connect request to the
    00127  * server through that socket. The server will then create a new socket pair, and pass one of
    00128  * them back. This new socket pair represents the newly established connection.
    00129  *
    00130  * @ingroup Support
    00131  */
    00132 class ApplicationPoolServer {
    00133 private:
    00134         /**
    00135          * Contains data shared between RemoteSession and Client.
    00136          * Since RemoteSession and Client have different life times, i.e. one may be
    00137          * destroyed before the other, they both use a smart pointer that points to
    00138          * a SharedData. This way, the SharedData object is only destroyed when
    00139          * both the RemoteSession and the Client object has been destroyed.
    00140          */
    00141         struct SharedData {
    00142                 /**
    00143                  * The socket connection to the ApplicationPool server, as was
    00144                  * established by ApplicationPoolServer::connect().
    00145                  *
    00146                  * The value may be -1, which indicates that the connection has
    00147                  * been closed.
    00148                  */
    00149                 int server;
    00150
    00151                 mutable boost::mutex lock;
    00152
    00153                 ~SharedData() {
    00154                         TRACE_POINT();
    00155                         if (server != -1) {
    00156                                 disconnect();
    00157                         }
    00158                 }
    00159
    00160                 /**
    00161                  * Disconnect from the ApplicationPool server.
    00162                  */
    00163                 void disconnect() {
    00164                         TRACE_POINT();
    00165                         int ret;
    00166                         do {
    00167                                 ret = close(server);
    00168                         } while (ret == -1 && errno == EINTR);
    00169                         server = -1;
    00170                 }
    00171         };
    00172
    00173         typedef shared_ptr<SharedData> SharedDataPtr;
    00174
    00175         /**
    00176          * An Application::Session which works together with ApplicationPoolServer.
    00177          */
    00178         class RemoteSession: public Application::Session {
    00179         private:
    00180                 SharedDataPtr data;
    00181                 int id;
    00182                 int fd;
    00183                 pid_t pid;
    00184         public:
    00185                 RemoteSession(SharedDataPtr data, pid_t pid, int id, int fd) {
    00186                         this->data = data;
    00187                         this->pid = pid;
    00188                         this->id = id;
    00189                         this->fd = fd;
    00190                 }
    00191
    00192                 virtual ~RemoteSession() {
    00193                         closeStream();
    00194                         boost::mutex::scoped_lock(data->lock);
    00195                         MessageChannel(data->server).write("close", toString(id).c_str(), NULL);
    00196                 }
    00197
    00198                 virtual int getStream() const {
    00199                         return fd;
    00200                 }
    00201
    00202                 virtual void setReaderTimeout(unsigned int msec) {
    00203                         MessageChannel(fd).setReadTimeout(msec);
    00204                 }
    00205
    00206                 virtual void setWriterTimeout(unsigned int msec) {
    00207                         MessageChannel(fd).setWriteTimeout(msec);
    00208                 }
    00209
    00210                 virtual void shutdownReader() {
    00211                         if (fd != -1) {
    00212                                 int ret = syscalls::shutdown(fd, SHUT_RD);
    00213                                 if (ret == -1) {
    00214                                         throw SystemException("Cannot shutdown the writer stream",
    00215                                                 errno);
    00216                                 }
    00217                         }
    00218                 }
    00219
    00220                 virtual void shutdownWriter() {
    00221                         if (fd != -1) {
    00222                                 int ret = syscalls::shutdown(fd, SHUT_WR);
    00223                                 if (ret == -1) {
    00224                                         throw SystemException("Cannot shutdown the writer stream",
    00225                                                 errno);
    00226                                 }
    00227                         }
    00228                 }
    00229
    00230                 virtual void closeStream() {
    00231                         if (fd != -1) {
    00232                                 int ret = syscalls::close(fd);
    00233                                 if (ret == -1) {
    00234                                         throw SystemException("Cannot close the session stream",
    00235                                                 errno);
    00236                                 }
    00237                                 fd = -1;
    00238                         }
    00239                 }
    00240
    00241                 virtual void discardStream() {
    00242                         fd = -1;
    00243                 }
    00244
    00245                 virtual pid_t getPid() const {
    00246                         return pid;
    00247                 }
    00248         };
    00249
    00250         /**
    00251          * An ApplicationPool implementation that works together with ApplicationPoolServer.
    00252          * It doesn't do much by itself, its job is mostly to forward queries/commands to
    00253          * the server and returning the result. Most of the logic is in the server executable.
    00254          */
    00255         class Client: public ApplicationPool {
    00256         private:
    00257                 // The smart pointer only serves to keep the shared data alive.
    00258                 // We access the shared data via a normal pointer, for performance.
    00259                 SharedDataPtr dataSmartPointer;
    00260                 SharedData *data;
    00261
    00262         public:
    00263                 /**
    00264                  * Create a new Client.
    00265                  *
    00266                  * @param sock The newly established socket connection with the ApplicationPoolServer.
    00267                  */
    00268                 Client(int sock) {
    00269                         dataSmartPointer = ptr(new SharedData());
    00270                         data = dataSmartPointer.get();
    00271                         data->server = sock;
    00272                 }
    00273
    00274                 virtual bool connected() const {
    00275                         boost::mutex::scoped_lock(data->lock);
    00276                         return data->server != -1;
    00277                 }
    00278
    00279                 virtual void clear() {
    00280                         MessageChannel channel(data->server);
    00281                         boost::mutex::scoped_lock l(data->lock);
    00282                         try {
    00283                                 channel.write("clear", NULL);
    00284                         } catch (...) {
    00285                                 data->disconnect();
    00286                                 throw;
    00287                         }
    00288                 }
    00289
    00290                 virtual void setMaxIdleTime(unsigned int seconds) {
    00291                         MessageChannel channel(data->server);
    00292                         boost::mutex::scoped_lock l(data->lock);
    00293                         try {
    00294                                 channel.write("setMaxIdleTime", toString(seconds).c_str(), NULL);
    00295                         } catch (...) {
    00296                                 data->disconnect();
    00297                                 throw;
    00298                         }
    00299                 }
    00300
    00301                 virtual void setMax(unsigned int max) {
    00302                         MessageChannel channel(data->server);
    00303                         boost::mutex::scoped_lock l(data->lock);
    00304                         try {
    00305                                 channel.write("setMax", toString(max).c_str(), NULL);
    00306                         } catch (...) {
    00307                                 data->disconnect();
    00308                                 throw;
    00309                         }
    00310                 }
    00311
    00312                 virtual unsigned int getActive() const {
    00313                         MessageChannel channel(data->server);
    00314                         boost::mutex::scoped_lock l(data->lock);
    00315                         vector<string> args;
    00316
    00317                         try {
    00318                                 channel.write("getActive", NULL);
    00319                                 channel.read(args);
    00320                                 return atoi(args[0].c_str());
    00321                         } catch (...) {
    00322                                 data->disconnect();
    00323                                 throw;
    00324                         }
    00325                 }
    00326
    00327                 virtual unsigned int getCount() const {
    00328                         MessageChannel channel(data->server);
    00329                         boost::mutex::scoped_lock l(data->lock);
    00330                         vector<string> args;
    00331
    00332                         try {
    00333                                 channel.write("getCount", NULL);
    00334                                 channel.read(args);
    00335                                 return atoi(args[0].c_str());
    00336                         } catch (...) {
    00337                                 data->disconnect();
    00338                                 throw;
    00339                         }
    00340                 }
    00341
    00342                 virtual void setMaxPerApp(unsigned int max) {
    00343                         MessageChannel channel(data->server);
    00344                         boost::mutex::scoped_lock l(data->lock);
    00345                         try {
    00346                                 channel.write("setMaxPerApp", toString(max).c_str(), NULL);
    00347                         } catch (...) {
    00348                                 data->disconnect();
    00349                                 throw;
    00350                         }
    00351                 }
    00352
    00353                 virtual pid_t getSpawnServerPid() const {
    00354                         this_thread::disable_syscall_interruption dsi;
    00355                         MessageChannel channel(data->server);
    00356                         boost::mutex::scoped_lock l(data->lock);
    00357                         vector<string> args;
    00358
    00359                         try {
    00360                                 channel.write("getSpawnServerPid", NULL);
    00361                                 channel.read(args);
    00362                                 return atoi(args[0].c_str());
    00363                         } catch (...) {
    00364                                 data->disconnect();
    00365                                 throw;
    00366                         }
    00367                 }
    00368
    00369                 virtual Application::SessionPtr get(const PoolOptions &options) {
    00370                         this_thread::disable_syscall_interruption dsi;
    00371                         TRACE_POINT();
    00372
    00373                         MessageChannel channel(data->server);
    00374                         boost::mutex::scoped_lock l(data->lock);
    00375                         vector<string> args;
    00376                         int stream;
    00377                         bool result;
    00378
    00379                         try {
    00380                                 vector<string> args;
    00381
    00382                                 args.push_back("get");
    00383                                 options.toVector(args);
    00384                                 channel.write(args);
    00385                         } catch (const SystemException &e) {
    00386                                 UPDATE_TRACE_POINT();
    00387                                 data->disconnect();
    00388
    00389                                 string message("Could not send data to the ApplicationPool server: ");
    00390                                 message.append(e.brief());
    00391                                 throw SystemException(message, e.code());
    00392                         }
    00393                         try {
    00394                                 UPDATE_TRACE_POINT();
    00395                                 result = channel.read(args);
    00396                         } catch (const SystemException &e) {
    00397                                 UPDATE_TRACE_POINT();
    00398                                 data->disconnect();
    00399                                 throw SystemException("Could not read a message from "
    00400                                         "the ApplicationPool server", e.code());
    00401                         }
    00402                         if (!result) {
    00403                                 UPDATE_TRACE_POINT();
    00404                                 data->disconnect();
    00405                                 throw IOException("The ApplicationPool server unexpectedly "
    00406                                         "closed the connection while we're reading a response "
    00407                                         "for the 'get' command.");
    00408                         }
    00409                         if (args[0] == "ok") {
    00410                                 UPDATE_TRACE_POINT();
    00411                                 pid_t pid = (pid_t) atol(args[1]);
    00412                                 int sessionID = atoi(args[2]);
    00413
    00414                                 try {
    00415                                         stream = channel.readFileDescriptor();
    00416                                 } catch (...) {
    00417                                         UPDATE_TRACE_POINT();
    00418                                         data->disconnect();
    00419                                         throw;
    00420                                 }
    00421
    00422                                 return ptr(new RemoteSession(dataSmartPointer,
    00423                                         pid, sessionID, stream));
    00424                         } else if (args[0] == "SpawnException") {
    00425                                 UPDATE_TRACE_POINT();
    00426                                 if (args[2] == "true") {
    00427                                         string errorPage;
    00428
    00429                                         try {
    00430                                                 result = channel.readScalar(errorPage);
    00431                                         } catch (...) {
    00432                                                 data->disconnect();
    00433                                                 throw;
    00434                                         }
    00435                                         if (!result) {
    00436                                                 throw IOException("The ApplicationPool server "
    00437                                                         "unexpectedly closed the connection while "
    00438                                                         "we're reading the error page data.");
    00439                                         }
    00440                                         throw SpawnException(args[1], errorPage);
    00441                                 } else {
    00442                                         throw SpawnException(args[1]);
    00443                                 }
    00444                         } else if (args[0] == "BusyException") {
    00445                                 UPDATE_TRACE_POINT();
    00446                                 throw BusyException(args[1]);
    00447                         } else if (args[0] == "IOException") {
    00448                                 UPDATE_TRACE_POINT();
    00449                                 data->disconnect();
    00450                                 throw IOException(args[1]);
    00451                         } else {
    00452                                 UPDATE_TRACE_POINT();
    00453                                 data->disconnect();
    00454                                 throw IOException("The ApplicationPool server returned "
    00455                                         "an unknown message: " + toString(args));
    00456                         }
    00457                 }
    00458         };
    00459
    00460
    00461         static const int SERVER_SOCKET_FD = 3;
    00462
    00463         string m_serverExecutable;
    00464         string m_spawnServerCommand;
    00465         string m_logFile;
    00466         string m_rubyCommand;
    00467         string m_user;
    00468         string statusReportFIFO;
    00469
    00470         /**
    00471          * The PID of the ApplicationPool server process. If no server process
    00472          * is running, then <tt>serverPid == 0</tt>.
    00473          *
    00474          * @invariant
    00475          *    if serverPid == 0:
    00476          *       serverSocket == -1
    00477          */
    00478         pid_t serverPid;
    00479
    00480         /**
    00481          * The connection to the ApplicationPool server process. If no server
    00482          * process is running, then <tt>serverSocket == -1</tt>.
    00483          *
    00484          * @invariant
    00485          *    if serverPid == 0:
    00486          *       serverSocket == -1
    00487          */
    00488         int serverSocket;
    00489
    00490         /**
    00491          * Shutdown the currently running ApplicationPool server process.
    00492          *
    00493          * @pre System call interruption is disabled.
    00494          * @pre serverSocket != -1 && serverPid != 0
    00495          * @post serverSocket == -1 && serverPid == 0
    00496          */
    00497         void shutdownServer() {
    00498                 TRACE_POINT();
    00499                 this_thread::disable_syscall_interruption dsi;
    00500                 int ret, status;
    00501                 time_t begin;
    00502                 bool done = false;
    00503
    00504                 syscalls::close(serverSocket);
    00505                 if (!statusReportFIFO.empty()) {
    00506                         do {
    00507                                 ret = unlink(statusReportFIFO.c_str());
    00508                         } while (ret == -1 && errno == EINTR);
    00509                 }
    00510
    00511                 P_TRACE(2, "Waiting for existing ApplicationPoolServerExecutable (PID " <<
    00512                         serverPid << ") to exit...");
    00513                 begin = syscalls::time(NULL);
    00514                 while (!done && syscalls::time(NULL) < begin + 5) {
    00515                         /*
    00516                          * Some Apache modules fork(), but don't close file descriptors.
    00517                          * mod_wsgi is one such example. Because of that, closing serverSocket
    00518                          * won't always cause the ApplicationPool server to exit. So we send it a
    00519                          * signal. This must be the same as the oxt/system_calls.hpp interruption
    00520                          * signal.
    00521                          */
    00522                         syscalls::kill(serverPid, SIGINT);
    00523
    00524                         ret = syscalls::waitpid(serverPid, &status, WNOHANG);
    00525                         done = ret > 0 || ret == -1;
    00526                         if (!done) {
    00527                                 syscalls::usleep(100000);
    00528                         }
    00529                 }
    00530                 if (done) {
    00531                         if (ret > 0) {
    00532                                 if (WIFEXITED(status)) {
    00533                                         P_TRACE(2, "ApplicationPoolServerExecutable exited with exit status " <<
    00534                                                 WEXITSTATUS(status) << ".");
    00535                                 } else if (WIFSIGNALED(status)) {
    00536                                         P_TRACE(2, "ApplicationPoolServerExecutable exited because of signal " <<
    00537                                                 WTERMSIG(status) << ".");
    00538                                 } else {
    00539                                         P_TRACE(2, "ApplicationPoolServerExecutable exited for an unknown reason.");
    00540                                 }
    00541                         } else {
    00542                                 P_TRACE(2, "ApplicationPoolServerExecutable exited.");
    00543                         }
    00544                 } else {
    00545                         P_DEBUG("ApplicationPoolServerExecutable not exited in time. Killing it...");
    00546                         syscalls::kill(serverPid, SIGKILL);
    00547                         syscalls::waitpid(serverPid, NULL, 0);
    00548                 }
    00549
    00550                 serverSocket = -1;
    00551                 serverPid = 0;
    00552         }
    00553
    00554         /**
    00555          * Start an ApplicationPool server process. If there's already one running,
    00556          * then the currently running one will be shutdown.
    00557          *
    00558          * @pre System call interruption is disabled.
    00559          * @post serverSocket != -1 && serverPid != 0
    00560          * @throw SystemException Something went wrong.
    00561          */
    00562         void restartServer() {
    00563                 TRACE_POINT();
    00564                 int fds[2];
    00565                 pid_t pid;
    00566
    00567                 if (serverPid != 0) {
    00568                         shutdownServer();
    00569                 }
    00570
    00571                 if (syscalls::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
    00572                         throw SystemException("Cannot create a Unix socket pair", errno);
    00573                 }
    00574
    00575                 createStatusReportFIFO();
    00576
    00577                 pid = syscalls::fork();
    00578                 if (pid == 0) { // Child process.
    00579                         dup2(STDERR_FILENO, STDOUT_FILENO);  // Redirect stdout to the same channel as stderr.
    00580                         dup2(fds[0], SERVER_SOCKET_FD);
    00581
    00582                         // Close all unnecessary file descriptors
    00583                         for (long i = sysconf(_SC_OPEN_MAX) - 1; i > SERVER_SOCKET_FD; i--) {
    00584                                 close(i);
    00585                         }
    00586
    00587                         execlp(
    00588                                 #if 0
    00589                                         "valgrind",
    00590                                         "valgrind",
    00591                                 #else
    00592                                         m_serverExecutable.c_str(),
    00593                                 #endif
    00594                                 m_serverExecutable.c_str(),
    00595                                 toString(Passenger::getLogLevel()).c_str(),
    00596                                 m_spawnServerCommand.c_str(),
    00597                                 m_logFile.c_str(),
    00598                                 m_rubyCommand.c_str(),
    00599                                 m_user.c_str(),
    00600                                 statusReportFIFO.c_str(),
    00601                                 (char *) 0);
    00602                         int e = errno;
    00603                         fprintf(stderr, "*** Passenger ERROR (%s:%d):\n"
    00604                                 "Cannot execute %s: %s (%d)\n",
    00605                                 __FILE__, __LINE__,
    00606                                 m_serverExecutable.c_str(), strerror(e), e);
    00607                         fflush(stderr);
    00608                         _exit(1);
    00609                 } else if (pid == -1) { // Error.
    00610                         syscalls::close(fds[0]);
    00611                         syscalls::close(fds[1]);
    00612                         throw SystemException("Cannot create a new process", errno);
    00613                 } else { // Parent process.
    00614                         syscalls::close(fds[0]);
    00615                         serverSocket = fds[1];
    00616
    00617                         int flags = fcntl(serverSocket, F_GETFD);
    00618                         if (flags != -1) {
    00619                                 fcntl(serverSocket, F_SETFD, flags | FD_CLOEXEC);
    00620                         }
    00621
    00622                         serverPid = pid;
    00623                 }
    00624         }
    00625
    00626         void createStatusReportFIFO() {
    00627                 TRACE_POINT();
    00628                 char filename[PATH_MAX];
    00629                 int ret;
    00630                 mode_t permissions = S_IRUSR | S_IWUSR;
    00631
    00632                 createPassengerTempDir(getSystemTempDir(), m_user.empty(),
    00633                         "nobody", geteuid(), getegid());
    00634
    00635                 snprintf(filename, sizeof(filename), "%s/info/status.fifo",
    00636                                 getPassengerTempDir().c_str());
    00637                 filename[PATH_MAX - 1] = '\0';
    00638                 do {
    00639                         ret = mkfifo(filename, permissions);
    00640                 } while (ret == -1 && errno == EINTR);
    00641                 if (ret == -1 && errno != EEXIST) {
    00642                         int e = errno;
    00643                         P_WARN("*** WARNING: Could not create FIFO '" << filename <<
    00644                                 "': " << strerror(e) << " (" << e << ")" << endl <<
    00645                                 "Disabling Passenger ApplicationPool status reporting.");
    00646                         statusReportFIFO = "";
    00647                 } else {
    00648                         statusReportFIFO = filename;
    00649
    00650                         // It seems that the permissions passed to mkfifo()
    00651                         // aren't respected, so here we chmod the file.
    00652                         do {
    00653                                 ret = chmod(filename, permissions);
    00654                         } while (ret == -1 && errno == EINTR);
    00655
    00656                         // Set the FIFO's owner according to whether we're running as root
    00657                         // and whether user switching is enabled.
    00658                         if (geteuid() == 0 && !m_user.empty()) {
    00659                                 uid_t uid;
    00660                                 gid_t gid;
    00661
    00662                                 determineLowestUserAndGroup(m_user, uid, gid);
    00663                                 do {
    00664                                         ret = chown(filename, uid, gid);
    00665                                 } while (ret == -1 && errno == EINTR);
    00666                                 if (errno == -1) {
    00667                                         int e = errno;
    00668                                         P_WARN("*** WARNING: Unable to set the FIFO file '" <<
    00669                                                 filename << "' its owner and group to that of user " <<
    00670                                                 m_user << ": " << strerror(e) << " (" << e << ")");
    00671                                 }
    00672                         }
    00673                 }
    00674         }
    00675
    00676 public:
    00677         /**
    00678          * Create a new ApplicationPoolServer object.
    00679          *
    00680          * @param serverExecutable The filename of the ApplicationPool server
    00681          *            executable to use.
    00682          * @param spawnServerCommand The filename of the spawn server to use.
    00683          * @param logFile Specify a log file that the spawn server should use.
    00684          *            Messages on its standard output and standard error channels
    00685          *            will be written to this log file. If an empty string is
    00686          *            specified, no log file will be used, and the spawn server
    00687          *            will use the same standard output/error channels as the
    00688          *            current process.
    00689          * @param rubyCommand The Ruby interpreter's command.
    00690          * @param user The user that the spawn manager should run as. This
    00691          *             parameter only has effect if the current process is
    00692          *             running as root. If the empty string is given, or if
    00693          *             the <tt>user</tt> is not a valid username, then
    00694          *             the spawn manager will be run as the current user.
    00695          * @throws SystemException An error occured while trying to setup the spawn server
    00696          *            or the server socket.
    00697          * @throws IOException The specified log file could not be opened.
    00698          */
    00699         ApplicationPoolServer(const string &serverExecutable,
    00700                      const string &spawnServerCommand,
    00701                      const string &logFile = "",
    00702                      const string &rubyCommand = "ruby",
    00703                      const string &user = "")
    00704         : m_serverExecutable(serverExecutable),
    00705           m_spawnServerCommand(spawnServerCommand),
    00706           m_logFile(logFile),
    00707           m_rubyCommand(rubyCommand),
    00708           m_user(user) {
    00709                 TRACE_POINT();
    00710                 serverSocket = -1;
    00711                 serverPid = 0;
    00712                 this_thread::disable_syscall_interruption dsi;
    00713                 restartServer();
    00714         }
    00715
    00716         ~ApplicationPoolServer() {
    00717                 TRACE_POINT();
    00718                 if (serverSocket != -1) {
    00719                         UPDATE_TRACE_POINT();
    00720                         this_thread::disable_syscall_interruption dsi;
    00721                         shutdownServer();
    00722                 }
    00723         }
    00724
    00725         /**
    00726          * Connects to the server and returns a usable ApplicationPool object.
    00727          * All cache/pool data of this ApplicationPool is actually stored on
    00728          * the server and shared with other clients, but that is totally
    00729          * transparent to the user of the ApplicationPool object.
    00730          *
    00731          * @note
    00732          *   All methods of the returned ApplicationPool object may throw
    00733          *   SystemException, IOException or boost::thread_interrupted.
    00734          *
    00735          * @warning
    00736          * One may only use the returned ApplicationPool object for handling
    00737          * one session at a time. For example, don't do stuff like this:
    00738          * @code
    00739          *   ApplicationPoolPtr pool = server.connect();
    00740          *   Application::SessionPtr session1 = pool->get(...);
    00741          *   Application::SessionPtr session2 = pool->get(...);
    00742          * @endcode
    00743          * Otherwise, a deadlock can occur under certain circumstances.
    00744          * @warning
    00745          * Instead, one should call connect() multiple times:
    00746          * @code
    00747          *   ApplicationPoolPtr pool1 = server.connect();
    00748          *   Application::SessionPtr session1 = pool1->get(...);
    00749          *
    00750          *   ApplicationPoolPtr pool2 = server.connect();
    00751          *   Application::SessionPtr session2 = pool2->get(...);
    00752          * @endcode
    00753          *
    00754          * @throws SystemException Something went wrong.
    00755          * @throws IOException Something went wrong.
    00756          */
    00757         ApplicationPoolPtr connect() {
    00758                 TRACE_POINT();
    00759                 try {
    00760                         this_thread::disable_syscall_interruption dsi;
    00761                         MessageChannel channel(serverSocket);
    00762                         int clientConnection;
    00763
    00764                         // Write some random data to wake up the server.
    00765                         channel.writeRaw("x", 1);
    00766
    00767                         clientConnection = channel.readFileDescriptor();
    00768                         return ptr(new Client(clientConnection));
    00769                 } catch (const SystemException &e) {
    00770                         throw SystemException("Could not connect to the ApplicationPool server", e.code());
    00771                 } catch (const IOException &e) {
    00772                         string message("Could not connect to the ApplicationPool server: ");
    00773                         message.append(e.what());
    00774                         throw IOException(message);
    00775                 }
    00776         }
    00777
    00778         /**
    00779          * Detach the server, thereby telling it that we don't want to connect
    00780          * to it anymore. This frees up some resources in the current process,
    00781          * such as file descriptors.
    00782          *
    00783          * This method is particularily useful to Apache worker processes that
    00784          * have just established a connection with the ApplicationPool server.
    00785          * Any sessions that are opened prior to calling detach(), will keep
    00786          * working even after a detach().
    00787          *
    00788          * This method may only be called once. The ApplicationPoolServer object
    00789          * will become unusable once detach() has been called, so call connect()
    00790          * before calling detach().
    00791          */
    00792         void detach() {
    00793                 TRACE_POINT();
    00794                 int ret;
    00795                 do {
    00796                         ret = close(serverSocket);
    00797                 } while (ret == -1 && errno == EINTR);
    00798                 serverSocket = -1;
    00799         }
    00800 };
    00801
    00802 typedef shared_ptr<ApplicationPoolServer> ApplicationPoolServerPtr;
    00803
    00804 } // namespace Passenger
    00805
    00806 #endif /* _PASSENGER_APPLICATION_POOL_SERVER_H_ */