ext/common/ApplicationPoolStatusReporter.h in passenger-2.2.2 vs ext/common/ApplicationPoolStatusReporter.h in passenger-2.2.3
- old
+ new
@@ -31,17 +31,20 @@
#include <oxt/backtrace.hpp>
#include <oxt/system_calls.hpp>
#include <string>
#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <sys/stat.h>
#include <cstdio>
#include <unistd.h>
#include <errno.h>
#include "StandardApplicationPool.h"
#include "MessageChannel.h"
+#include "Exceptions.h"
#include "Logging.h"
#include "Utils.h"
namespace Passenger {
@@ -49,76 +52,180 @@
using namespace oxt;
using namespace std;
/**
* An ApplicationPoolStatusReporter allows commandline admin tools to inspect
- * the status of a StandardApplicationPool. It does so by creating a FIFO
- * in the Passenger temp folder.
+ * the status of a StandardApplicationPool. It does so by creating a Unix socket
+ * in the Passenger temp folder, which tools can connect to to query for
+ * information.
*
- * An ApplicationPoolStatusReporter creates a background thread, which
- * continuously sends new information through the FIFO. This thread will
- * be automatically cleaned up upon destroying the ApplicationPoolStatusReporter
- * object.
+ * An ApplicationPoolStatusReporter creates a background thread for handling
+ * connections on the socket. This thread will be automatically cleaned up upon
+ * destroying the ApplicationPoolStatusReporter object.
*/
class ApplicationPoolStatusReporter {
private:
+ /**
+ * Wrapper class around a file descriptor integer, for RAII behavior.
+ *
+ * A FileDescriptor object behaves just like an int, so that you can pass it to
+ * system calls such as read(). It performs reference counting. When the last
+ * copy of a FileDescriptor has been destroyed, the underlying file descriptor
+ * will be automatically closed.
+ */
+ class FileDescriptor {
+ private:
+ struct SharedData {
+ int fd;
+
+ /**
+ * Constructor to assign this file descriptor's handle.
+ */
+ SharedData(int fd) {
+ this->fd = fd;
+ }
+
+ /**
+ * Attempts to close this file descriptor. When created on the stack,
+ * this destructor will automatically be invoked as a result of C++
+ * semantics when exiting the scope this object was created in. This
+ * ensures that stack created objects with destructors like these will
+ * de-allocate their resources upon leaving their corresponding scope.
+ * This pattern is also known Resource Acquisition Is Initialization (RAII).
+ *
+ * @throws SystemException File descriptor could not be closed.
+ */
+ ~SharedData() {
+ this_thread::disable_syscall_interruption dsi;
+ if (syscalls::close(fd) == -1) {
+ throw SystemException("Cannot close file descriptor", errno);
+ }
+ }
+ };
+
+ /* Shared pointer for reference counting on this file descriptor */
+ shared_ptr<SharedData> data;
+
+ public:
+ FileDescriptor() {
+ // Do nothing.
+ }
+
+ /**
+ * Creates a new FileDescriptor instance with the given fd as a handle.
+ */
+ FileDescriptor(int fd) {
+ data = ptr(new SharedData(fd));
+ }
+
+ /**
+ * Overloads the integer cast operator so that it will return the file
+ * descriptor handle as an integer.
+ *
+ * @return This file descriptor's handle as an integer.
+ */
+ operator int () const {
+ return data->fd;
+ }
+ };
+
/** The application pool to monitor. */
StandardApplicationPoolPtr pool;
- /** The FIFO's filename. */
+ /** The socket's filename. */
char filename[PATH_MAX];
- /** The background thread. */
- oxt::thread *thr;
+ /** The socket's file descriptor. */
+ int serverFd;
- void threadMain() {
+ /** The main thread. */
+ oxt::thread *mainThread;
+
+ /** The mutex which protects the 'threads' member. */
+ boost::mutex threadsLock;
+
+ /** A map which maps a client file descriptor to its handling thread. */
+ map< int, shared_ptr<oxt::thread> > threads;
+
+ void writeScalarAndIgnoreErrors(MessageChannel &channel, const string &data) {
+ try {
+ channel.writeScalar(data);
+ } catch (const SystemException &e) {
+ // Don't care about write errors.
+ }
+ }
+
+ void mainThreadFunction() {
TRACE_POINT();
try {
while (!this_thread::interruption_requested()) {
- struct stat buf;
- int ret;
-
UPDATE_TRACE_POINT();
- do {
- ret = stat(filename, &buf);
- } while (ret == -1 && errno == EINTR);
- if (ret == -1 || !S_ISFIFO(buf.st_mode)) {
- // Something bad happened with the status
- // report FIFO, so we bail out.
- break;
- }
+ sockaddr_un addr;
+ socklen_t addr_len = sizeof(addr);
- UPDATE_TRACE_POINT();
- FILE *f = syscalls::fopen(filename, "w");
- if (f == NULL) {
+ FileDescriptor fd(syscalls::accept(serverFd, (struct sockaddr *) &addr, &addr_len));
+ if (fd == -1) {
int e = errno;
- P_ERROR("Cannot open status report FIFO " <<
- filename << ": " <<
+ P_ERROR("Cannot accept new client on status reporter socket: " <<
strerror(e) << " (" << e << ")");
break;
}
- UPDATE_TRACE_POINT();
- MessageChannel channel(fileno(f));
- string report;
- report.append("----------- Backtraces -----------\n");
- report.append(oxt::thread::all_backtraces());
- report.append("\n\n");
- report.append(pool->toString());
+ boost::lock_guard<boost::mutex> l(threadsLock);
+ this_thread::disable_syscall_interruption dsi;
+ this_thread::disable_interruption di;
+ shared_ptr<oxt::thread> thread(new oxt::thread(
+ bind(&ApplicationPoolStatusReporter::clientThreadFunction, this, fd),
+ "Status reporter client thread " + toString(fd),
+ 1024 * 128
+ ));
+ threads[fd] = thread;
+ }
+ } catch (const boost::thread_interrupted &) {
+ P_TRACE(2, "Status reporter main thread interrupted.");
+ } catch (const exception &e) {
+ P_ERROR("Error in status reporter main thread: " << e.what());
+ }
+ }
+
+ void clientThreadFunction(FileDescriptor fd) {
+ TRACE_POINT();
+ MessageChannel channel(fd);
+
+ try {
+ while (!this_thread::interruption_requested()) {
+ vector<string> args;
UPDATE_TRACE_POINT();
- try {
- channel.writeScalar(report);
- channel.writeScalar(pool->toXml());
- } catch (...) {
- // Ignore write errors.
+ if (!channel.read(args) || args.size() < 1) {
+ break;
}
- syscalls::fclose(f);
+
+ if (args[0] == "backtraces") {
+ UPDATE_TRACE_POINT();
+ writeScalarAndIgnoreErrors(channel, oxt::thread::all_backtraces());
+ } else if (args[0] == "status") {
+ UPDATE_TRACE_POINT();
+ writeScalarAndIgnoreErrors(channel, pool->toString());
+ } else if (args[0] == "status_xml") {
+ UPDATE_TRACE_POINT();
+ writeScalarAndIgnoreErrors(channel, pool->toXml());
+ } else {
+ P_ERROR("Error in status reporter client thread: unknown query '" <<
+ args[0] << "'.");
+ }
}
} catch (const boost::thread_interrupted &) {
- P_TRACE(2, "Status report thread interrupted.");
+ P_TRACE(2, "Status reporter client thread " << fd << " interrupted.");
+ } catch (const exception &e) {
+ P_ERROR("Error in status reporter client thread: " << e.what());
}
+
+ boost::lock_guard<boost::mutex> l(threadsLock);
+ this_thread::disable_syscall_interruption dsi;
+ this_thread::disable_interruption di;
+ threads.erase(fd);
}
public:
/**
* Creates a new ApplicationPoolStatusReporter.
@@ -132,13 +239,15 @@
* be created.
* @param uid The UID of the user who should own the FIFO file, or
* -1 if the current user should be set as owner.
* @param gid The GID of the user who should own the FIFO file, or
* -1 if the current group should be set as group.
- * @throws SystemException An error occurred while creating the FIFO.
+ * @throws RuntimeException An error occurred.
+ * @throws SystemException An error occurred while creating the server socket.
* @throws boost::thread_resource_error Something went wrong during
* creation of the thread.
+ * @throws boost::thread_interrupted A system call has been interrupted.
*/
ApplicationPoolStatusReporter(StandardApplicationPoolPtr &pool,
bool userSwitching,
mode_t permissions = S_IRUSR | S_IWUSR,
uid_t uid = -1, gid_t gid = -1) {
@@ -147,64 +256,79 @@
this->pool = pool;
createPassengerTempDir(getSystemTempDir(), userSwitching,
"nobody", geteuid(), getegid());
- snprintf(filename, sizeof(filename) - 1, "%s/info/status.fifo",
+ snprintf(filename, sizeof(filename) - 1, "%s/info/status.socket",
getPassengerTempDir().c_str());
filename[PATH_MAX - 1] = '\0';
+ serverFd = createUnixServer(filename, 10);
+ /* Set the socket file's permissions... */
do {
- ret = mkfifo(filename, permissions);
- } while (ret == -1 && errno == EINTR);
- if (ret == -1 && errno != EEXIST) {
- int e = errno;
- string message("Cannot create FIFO '");
- message.append(filename);
- message.append("'");
- throw SystemException(message, e);
- }
-
- // It seems that the permissions passed to mkfifo()
- // aren't respected, so here we chmod the file.
- do {
ret = chmod(filename, permissions);
} while (ret == -1 && errno == EINTR);
+ /* ...and ownership. */
if (uid != (uid_t) -1 && gid != (gid_t) -1) {
do {
ret = chown(filename, uid, gid);
} while (ret == -1 && errno == EINTR);
if (errno == -1) {
int e = errno;
char message[1024];
snprintf(message, sizeof(message) - 1,
- "Cannot set the FIFO file '%s' its owner to %lld and group to %lld",
+ "Cannot set the owner for socket file '%s' to %lld and its group to %lld",
filename, (long long) uid, (long long) gid);
message[sizeof(message) - 1] = '\0';
+ do {
+ ret = close(serverFd);
+ } while (ret == -1 && errno == EINTR);
throw SystemException(message, e);
}
}
- thr = new oxt::thread(
- bind(&ApplicationPoolStatusReporter::threadMain, this),
- "Status report thread",
- 1024 * 128
- );
+ try {
+ mainThread = new oxt::thread(
+ bind(&ApplicationPoolStatusReporter::mainThreadFunction, this),
+ "Status reporter main thread",
+ 1024 * 128
+ );
+ } catch (...) {
+ do {
+ ret = close(serverFd);
+ } while (ret == -1 && errno == EINTR);
+ throw;
+ }
}
~ApplicationPoolStatusReporter() {
this_thread::disable_syscall_interruption dsi;
this_thread::disable_interruption di;
-
- thr->interrupt_and_join();
- delete thr;
-
int ret;
+
do {
ret = unlink(filename);
} while (ret == -1 && errno == EINTR);
+
+ mainThread->interrupt_and_join();
+ delete mainThread;
+
+ do {
+ ret = close(serverFd);
+ } while (ret == -1 && errno == EINTR);
+
+ /* We make a copy of the data structure here to avoid deadlocks. */
+ map< int, shared_ptr<oxt::thread> > threadsCopy;
+ {
+ boost::lock_guard<boost::mutex> l(threadsLock);
+ threadsCopy = threads;
+ }
+ map< int, shared_ptr<oxt::thread> >::iterator it;
+ for (it = threadsCopy.begin(); it != threadsCopy.end(); it++) {
+ it->second->interrupt_and_join();
+ }
}
};
} // namespace Passenger