0
- * Phusion Passenger - http://www.modrails.com/
0
- * Copyright (C) 2008 Phusion
0
- * This program is free software; you can redistribute it and/or modify
0
- * it under the terms of the GNU General Public License as published by
0
- * the Free Software Foundation; version 2 of the License.
0
- * This program is distributed in the hope that it will be useful,
0
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
0
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0
- * GNU General Public License for more details.
0
- * You should have received a copy of the GNU General Public License along
0
- * with this program; if not, write to the Free Software Foundation, Inc.,
0
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0
-#ifndef _PASSENGER_APPLICATION_POOL_CLIENT_SERVER_H_
0
-#define _PASSENGER_APPLICATION_POOL_CLIENT_SERVER_H_
0
-#include <boost/bind.hpp>
0
-#include <boost/thread/thread.hpp>
0
-#include <sys/socket.h>
0
-#include "StandardApplicationPool.h"
0
-#include "MessageChannel.h"
0
-#include "Exceptions.h"
0
- * Multi-process usage support for ApplicationPool.
0
- * ApplicationPoolServer implements a client/server architecture for ApplicationPool.
0
- * This allows one to use ApplicationPool in a multi-process environment (unlike
0
- * StandardApplicationPool). The cache/pool data is stored in the server. Different
0
- * processes can then access the pool through the server.
0
- * ApplicationPoolServer itself does not inherit ApplicationPool. Instead, it returns
0
- * an ApplicationPool object via the connect() call. For example:
0
- * // Create an ApplicationPoolServer.
0
- * ApplicationPoolServer server(...);
0
- * // Now fork a child process, like Apache's prefork MPM eventually will.
0
- * // Connect to the server. After connection, we have an ApplicationPool
0
- * ApplicationPoolPtr pool(server.connect());
0
- * // The child process doesn't run a server (only the parent process does)
0
- * // so we call detach to free the server resources (things like file
0
- * ApplicationPool::SessionPtr session(pool->get("/home/webapps/foo"));
0
- * do_something_with(session);
0
- * waitpid(pid, NULL, 0);
0
- * ApplicationPoolServer uses threads internally. Threads will disappear after a fork(),
0
- * so ApplicationPoolServer will become usable as a server after a fork(). After a fork(),
0
- * you can still call connect() (and, of course, detach()), but the same
0
- * ApplicationPoolServer better still be running in the parent process. So in case of
0
- * Apache with the prefork MPM, be sure to create an ApplicationPoolServer
0
- * <em>after</em> Apache has daemonized.
0
- * <h2>Implementation notes</h2>
0
- * Notice that ApplicationPoolServer does do not use TCP sockets at all, or even named Unix
0
- * sockets, depite being a server that can handle multiple clients! So ApplicationPoolServer
0
- * will expose no open ports or temporary Unix socket files. Only child processes are able
0
- * to use the ApplicationPoolServer.
0
- * This is implemented through anonymous Unix sockets (<tt>socketpair()</tt>) and file descriptor
0
- * passing. It allows one to emulate <tt>accept()</tt>. During initialization,
0
- * ApplicationPoolServer creates a pair of Unix sockets, one called <tt>serverSocket</tt>
0
- * and the other called <tt>connectSocket</tt>. There is a thread which continuously
0
- * listens on serverSocket for incoming data. The data itself is not important, because it
0
- * only serves to wake up the thread. ApplicationPoolServer::connect() sends some data through
0
- * connectSocket, which wakes up the server thread. The server thread will then create
0
- * a pair of Unix sockets. One of them is passed through serverSocket. The other will be
0
- * handled by a newly created client thread. So the socket that was passed through serverSocket
0
- * is the client's connection to the server, while the other socket is the server's connection
0
- * Note that serverSocket and connectSocket are solely used for setting up new connections
0
- * ala accept(). They are not used for any actual data. In fact, they cannot be used in any
0
- * other way without some sort of inter-process synchronization mechanism, because all
0
- * child processes are connected to the same serverSocket. In contrast,
0
- * ApplicationPoolServer::connect() allows one to setup a private communicate channel between
0
- * the server and the current child process.
0
- * Also note that each client is handled by a seperate thread. This is necessary because
0
- * ApplicationPoolServer internally uses StandardApplicationPool, and the current algorithm
0
- * for StandardApplicationPool::get() can block (in the case that the spawning limit has
0
- * been exceeded). While it is possible to get around this problem without using threads,
0
- * a thread-based implementation is easier to write.
0
-class ApplicationPoolServer {
0
- * Contains data shared between RemoteSession and Client.
0
- * Since RemoteSession and Client have different life times, i.e. one may be
0
- * destroyed before the other, they both use a smart pointer that points to
0
- * a SharedData. This way, the SharedData object is only destroyed when
0
- * both the RemoteSession and the Client object has been destroyed.
0
- * The socket connection to the server, as was established by
0
- * ApplicationPoolServer::connect().
0
- typedef shared_ptr<SharedData> SharedDataPtr;
0
- * An Application::Session which works together with ApplicationPoolServer.
0
- class RemoteSession: public Application::Session {
0
- RemoteSession(SharedDataPtr data, pid_t pid, int id, int reader, int writer) {
0
- this->reader = reader;
0
- this->writer = writer;
0
- virtual ~RemoteSession() {
0
- MessageChannel(data->server).write("close", toString(id).c_str(), NULL);
0
- virtual int getReader() const {
0
- virtual void closeReader() {
0
- virtual int getWriter() const {
0
- virtual void closeWriter() {
0
- virtual pid_t getPid() const {
0
- * An ApplicationPool implementation that works together with ApplicationPoolServer.
0
- * It doesn't do much by itself, its job is mostly to forward queries/commands to
0
- * the server and returning the result. Most of the logic is in the server.
0
- class Client: public ApplicationPool {
0
- * Create a new Client.
0
- * @param sock The newly established socket connection with the ApplicationPoolServer.
0
- data = ptr(new SharedData());
0
- virtual void clear() {
0
- MessageChannel channel(data->server);
0
- channel.write("clear", NULL);
0
- virtual void setMaxIdleTime(unsigned int seconds) {
0
- MessageChannel channel(data->server);
0
- channel.write("setMaxIdleTime", toString(seconds).c_str(), NULL);
0
- virtual void setMax(unsigned int max) {
0
- MessageChannel channel(data->server);
0
- channel.write("setMax", toString(max).c_str(), NULL);
0
- virtual unsigned int getActive() const {
0
- MessageChannel channel(data->server);
0
- channel.write("getActive", NULL);
0
- return atoi(args[0].c_str());
0
- virtual unsigned int getCount() const {
0
- MessageChannel channel(data->server);
0
- channel.write("getCount", NULL);
0
- return atoi(args[0].c_str());
0
- virtual pid_t getSpawnServerPid() const {
0
- MessageChannel channel(data->server);
0
- channel.write("getSpawnServerPid", NULL);
0
- return atoi(args[0].c_str());
0
- virtual Application::SessionPtr get(const string &appRoot, bool lowerPrivilege = true, const string &lowestUser = "nobody") {
0
- MessageChannel channel(data->server);
0
- channel.write("get", appRoot.c_str(),
0
- (lowerPrivilege) ? "true" : "false",
0
- lowestUser.c_str(), NULL);
0
- if (!channel.read(args)) {
0
- throw IOException("The ApplicationPool server unexpectedly closed the connection.");
0
- if (args[0] == "ok") {
0
- reader = channel.readFileDescriptor();
0
- writer = channel.readFileDescriptor();
0
- return ptr(new RemoteSession(data, atoi(args[1]), atoi(args[2]), reader, writer));
0
- } else if (args[0] == "SpawnException") {
0
- if (args[2] == "true") {
0
- if (!channel.readScalar(errorPage)) {
0
- throw IOException("The ApplicationPool server unexpectedly closed the connection.");
0
- throw SpawnException(args[1], errorPage);
0
- throw SpawnException(args[1]);
0
- } else if (args[0] == "IOException") {
0
- throw IOException(args[1]);
0
- throw IOException("The ApplicationPool server returned an unknown message.");
0
- * Contains information about exactly one client.
0
- /** The connection to the client. */
0
- /** The thread which handles the client. */
0
- /* For some reason, joining or deleting (detaching)
0
- * the thread after fork() will cause a segfault.
0
- * I haven't figured out why that happens, so for now
0
- * I'll just ignore the thread (which isn't running
0
- typedef shared_ptr<ClientInfo> ClientInfoPtr;
0
- StandardApplicationPool pool;
0
- set<ClientInfoPtr> clients;
0
- /* TODO: the current design makes it possible to leak file descriptors.
0
- * For example, suppose that a fork() happens right after
0
- * serverThreadMainLoop() created a socketpair. Uh-oh. The problem is that
0
- * Apache can fork no matter what the threads are currently doing.
0
- * This problem can be solved by running the server thread main loop in
0
- * its own process, instead of a thread in the Apache control process.
0
- * This situation is a corner case though, and doesn't happen very often.
0
- * When it does happen, the problem isn't that great: an Apache worker
0
- * process will eventually get killed, thus freeing all its file
0
- * descriptors. So it should be acceptable to fix this problem in
0
- * a post-1.0.1 release.
0
- * The entry point of the server thread which sets up private connections.
0
- * See the class overview's implementation notes for details.
0
- void serverThreadMainLoop() {
0
- // The received data only serves to wake up the server socket,
0
- // and is not important.
0
- ret = read(serverSocket, &x, 1);
0
- } while (ret == -1 && errno == EINTR);
0
- // Incoming connect request.
0
- ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
0
- } while (ret == -1 && errno == EINTR);
0
- P_ERROR("Cannot create an anonymous Unix socket: " <<