- Main Page
- Modules
- Classes
- Files

# MessageChannel.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_MESSAGE_CHANNEL_H_
    00021 #define _PASSENGER_MESSAGE_CHANNEL_H_
    00022
    00023 #include <oxt/system_calls.hpp>
    00024
    00025 #include <algorithm>
    00026 #include <string>
    00027 #include <list>
    00028 #include <vector>
    00029
    00030 #include <sys/types.h>
    00031 #include <sys/socket.h>
    00032 #include <arpa/inet.h>
    00033 #include <errno.h>
    00034 #include <unistd.h>
    00035 #include <cstdarg>
    00036 #ifdef __OpenBSD__
    00037         // OpenBSD needs this for 'struct iovec'. Apparently it isn't
    00038         // always included by unistd.h and sys/types.h.
    00039         #include <sys/uio.h>
    00040 #endif
    00041 #if !APR_HAVE_IOVEC
    00042         // We don't want apr_want.h to redefine 'struct iovec'.
    00043         // http://tinyurl.com/b6aatw
    00044         #undef APR_HAVE_IOVEC
    00045         #define APR_HAVE_IOVEC 1
    00046 #endif
    00047
    00048 #include "Exceptions.h"
    00049 #include "Utils.h"
    00050
    00051 namespace Passenger {
    00052
    00053 using namespace std;
    00054 using namespace oxt;
    00055
    00056 /**
    00057  * Convenience class for I/O operations on file descriptors.
    00058  *
    00059  * This class provides convenience methods for:
    00060  *  - sending and receiving raw data over a file descriptor.
    00061  *  - sending and receiving messages over a file descriptor.
    00062  *  - file descriptor passing over a Unix socket.
    00063  * All of these methods use exceptions for error reporting.
    00064  *
    00065  * There are two kinds of messages:
    00066  *  - Array messages. These are just a list of strings, and the message
    00067  *    itself has a specific length. The contained strings may not
    00068  *    contain NUL characters (<tt>'\\0'</tt>). Note that an array message
    00069  *    must have at least one element.
    00070  *  - Scalar messages. These are byte strings which may contain arbitrary
    00071  *    binary data. Scalar messages also have a specific length.
    00072  * The protocol is designed to be low overhead, easy to implement and
    00073  * easy to parse.
    00074  *
    00075  * MessageChannel is to be wrapped around a file descriptor. For example:
    00076  * @code
    00077  *    int p[2];
    00078  *    pipe(p);
    00079  *    MessageChannel channel1(p[0]);
    00080  *    MessageChannel channel2(p[1]);
    00081  *
    00082  *    // Send an array message.
    00083  *    channel2.write("hello", "world !!", NULL);
    00084  *    list<string> args;
    00085  *    channel1.read(args);    // args now contains { "hello", "world !!" }
    00086  *
    00087  *    // Send a scalar message.
    00088  *    channel2.writeScalar("some long string which can contain arbitrary binary data");
    00089  *    string str;
    00090  *    channel1.readScalar(str);
    00091  * @endcode
    00092  *
    00093  * The life time of a MessageChannel is independent from that of the
    00094  * wrapped file descriptor. If a MessageChannel object is destroyed,
    00095  * the file descriptor is not automatically closed. Call close()
    00096  * if you want to close the file descriptor.
    00097  *
    00098  * @note I/O operations are not buffered.
    00099  * @note Be careful with mixing the sending/receiving of array messages,
    00100  *    scalar messages and file descriptors. If you send a collection of any
    00101  *    of these in a specific order, then the receiving side must receive them
    00102  *    in the exact some order. So suppose you first send a message, then a
    00103  *    file descriptor, then a scalar, then the receiving side must first
    00104  *    receive a message, then a file descriptor, then a scalar. If the
    00105  *    receiving side does things in the wrong order then bad things will
    00106  *    happen.
    00107  * @note MessageChannel is not thread-safe, but is reentrant.
    00108  *
    00109  * @ingroup Support
    00110  */
    00111 class MessageChannel {
    00112 private:
    00113         const static char DELIMITER = '\0';
    00114         int fd;
    00115
    00116         #ifdef __OpenBSD__
    00117                 typedef u_int32_t uint32_t;
    00118                 typedef u_int16_t uint16_t;
    00119         #endif
    00120
    00121 public:
    00122         /**
    00123          * Construct a new MessageChannel with no underlying file descriptor.
    00124          * Thus the resulting MessageChannel object will not be usable.
    00125          * This constructor exists to allow one to declare an "empty"
    00126          * MessageChannel variable which is to be initialized later.
    00127          */
    00128         MessageChannel() {
    00129                 this->fd = -1;
    00130         }
    00131
    00132         /**
    00133          * Construct a new MessageChannel with the given file descriptor.
    00134          */
    00135         MessageChannel(int fd) {
    00136                 this->fd = fd;
    00137         }
    00138
    00139         /**
    00140          * Close the underlying file descriptor. If this method is called multiple
    00141          * times, the file descriptor will only be closed the first time.
    00142          *
    00143          * @throw SystemException
    00144          * @throw boost::thread_interrupted
    00145          */
    00146         void close() {
    00147                 if (fd != -1) {
    00148                         int ret = syscalls::close(fd);
    00149                         if (ret == -1) {
    00150                                 throw SystemException("Cannot close file descriptor", errno);
    00151                         }
    00152                         fd = -1;
    00153                 }
    00154         }
    00155
    00156         /**
    00157          * Send an array message, which consists of the given elements, over the underlying
    00158          * file descriptor.
    00159          *
    00160          * @param args An object which contains the message elements. This object must
    00161          *             support STL-style iteration, and each iterator must have an
    00162          *             std::string as value. Use the StringArrayType and
    00163          *             StringArrayConstIteratorType template parameters to specify the exact type names.
    00164          * @throws SystemException An error occured while writing the data to the file descriptor.
    00165          * @throws boost::thread_interrupted
    00166          * @pre None of the message elements may contain a NUL character (<tt>'\\0'</tt>).
    00167          * @see read(), write(const char *, ...)
    00168          */
    00169         template<typename StringArrayType, typename StringArrayConstIteratorType>
    00170         void write(const StringArrayType &args) {
    00171                 StringArrayConstIteratorType it;
    00172                 string data;
    00173                 uint16_t dataSize = 0;
    00174
    00175                 for (it = args.begin(); it != args.end(); it++) {
    00176                         dataSize += it->size() + 1;
    00177                 }
    00178                 data.reserve(dataSize + sizeof(dataSize));
    00179                 dataSize = htons(dataSize);
    00180                 data.append((const char *) &dataSize, sizeof(dataSize));
    00181                 for (it = args.begin(); it != args.end(); it++) {
    00182                         data.append(*it);
    00183                         data.append(1, DELIMITER);
    00184                 }
    00185
    00186                 writeRaw(data);
    00187         }
    00188
    00189         /**
    00190          * Send an array message, which consists of the given elements, over the underlying
    00191          * file descriptor.
    00192          *
    00193          * @param args The message elements.
    00194          * @throws SystemException An error occured while writing the data to the file descriptor.
    00195          * @throws boost::thread_interrupted
    00196          * @pre None of the message elements may contain a NUL character (<tt>'\\0'</tt>).
    00197          * @see read(), write(const char *, ...)
    00198          */
    00199         void write(const list<string> &args) {
    00200                 write<list<string>, list<string>::const_iterator>(args);
    00201         }
    00202
    00203         /**
    00204          * Send an array message, which consists of the given elements, over the underlying
    00205          * file descriptor.
    00206          *
    00207          * @param args The message elements.
    00208          * @throws SystemException An error occured while writing the data to the file descriptor.
    00209          * @throws boost::thread_interrupted
    00210          * @pre None of the message elements may contain a NUL character (<tt>'\\0'</tt>).
    00211          * @see read(), write(const char *, ...)
    00212          */
    00213         void write(const vector<string> &args) {
    00214                 write<vector<string>, vector<string>::const_iterator>(args);
    00215         }
    00216
    00217         /**
    00218          * Send an array message, which consists of the given strings, over the underlying
    00219          * file descriptor.
    00220          *
    00221          * @param name The first element of the message to send.
    00222          * @param ... Other elements of the message. These *must* be strings, i.e. of type char*.
    00223          *            It is also required to terminate this list with a NULL.
    00224          * @throws SystemException An error occured while writing the data to the file descriptor.
    00225          * @throws boost::thread_interrupted
    00226          * @pre None of the message elements may contain a NUL character (<tt>'\\0'</tt>).
    00227          * @see read(), write(const list<string> &)
    00228          */
    00229         void write(const char *name, ...) {
    00230                 list<string> args;
    00231                 args.push_back(name);
    00232
    00233                 va_list ap;
    00234                 va_start(ap, name);
    00235                 while (true) {
    00236                         const char *arg = va_arg(ap, const char *);
    00237                         if (arg == NULL) {
    00238                                 break;
    00239                         } else {
    00240                                 args.push_back(arg);
    00241                         }
    00242                 }
    00243                 va_end(ap);
    00244                 write(args);
    00245         }
    00246
    00247         /**
    00248          * Send a scalar message over the underlying file descriptor.
    00249          *
    00250          * @param str The scalar message's content.
    00251          * @throws SystemException An error occured while writing the data to the file descriptor.
    00252          * @throws boost::thread_interrupted
    00253          * @see readScalar(), writeScalar(const char *, unsigned int)
    00254          */
    00255         void writeScalar(const string &str) {
    00256                 writeScalar(str.c_str(), str.size());
    00257         }
    00258
    00259         /**
    00260          * Send a scalar message over the underlying file descriptor.
    00261          *
    00262          * @param data The scalar message's content.
    00263          * @param size The number of bytes in <tt>data</tt>.
    00264          * @pre <tt>data != NULL</tt>
    00265          * @throws SystemException An error occured while writing the data to the file descriptor.
    00266          * @throws boost::thread_interrupted
    00267          * @see readScalar(), writeScalar(const string &)
    00268          */
    00269         void writeScalar(const char *data, unsigned int size) {
    00270                 uint32_t l = htonl(size);
    00271                 writeRaw((const char *) &l, sizeof(uint32_t));
    00272                 writeRaw(data, size);
    00273         }
    00274
    00275         /**
    00276          * Send a block of data over the underlying file descriptor.
    00277          * This method blocks until everything is sent.
    00278          *
    00279          * @param data The data to send.
    00280          * @param size The number of bytes in <tt>data</tt>.
    00281          * @pre <tt>data != NULL</tt>
    00282          * @throws SystemException An error occured while writing the data to the file descriptor.
    00283          * @throws boost::thread_interrupted
    00284          * @see readRaw()
    00285          */
    00286         void writeRaw(const char *data, unsigned int size) {
    00287                 ssize_t ret;
    00288                 unsigned int written = 0;
    00289                 do {
    00290                         ret = syscalls::write(fd, data + written, size - written);
    00291                         if (ret == -1) {
    00292                                 throw SystemException("write() failed", errno);
    00293                         } else {
    00294                                 written += ret;
    00295                         }
    00296                 } while (written < size);
    00297         }
    00298
    00299         /**
    00300          * Send a block of data over the underlying file descriptor.
    00301          * This method blocks until everything is sent.
    00302          *
    00303          * @param data The data to send.
    00304          * @pre <tt>data != NULL</tt>
    00305          * @throws SystemException An error occured while writing the data to the file descriptor.
    00306          * @throws boost::thread_interrupted
    00307          */
    00308         void writeRaw(const string &data) {
    00309                 writeRaw(data.c_str(), data.size());
    00310         }
    00311
    00312         /**
    00313          * Pass a file descriptor. This only works if the underlying file
    00314          * descriptor is a Unix socket.
    00315          *
    00316          * @param fileDescriptor The file descriptor to pass.
    00317          * @throws SystemException Something went wrong during file descriptor passing.
    00318          * @throws boost::thread_interrupted
    00319          * @pre <tt>fileDescriptor >= 0</tt>
    00320          * @see readFileDescriptor()
    00321          */
    00322         void writeFileDescriptor(int fileDescriptor) {
    00323                 struct msghdr msg;
    00324                 struct iovec vec;
    00325                 char dummy[1];
    00326                 #if defined(__APPLE__) || defined(__SOLARIS__)
    00327                         struct {
    00328                                 struct cmsghdr header;
    00329                                 int fd;
    00330                         } control_data;
    00331                 #else
    00332                         char control_data[CMSG_SPACE(sizeof(int))];
    00333                 #endif
    00334                 struct cmsghdr *control_header;
    00335                 int ret;
    00336
    00337                 msg.msg_name = NULL;
    00338                 msg.msg_namelen = 0;
    00339
    00340                 /* Linux and Solaris require msg_iov to be non-NULL. */
    00341                 dummy[0]       = '\0';
    00342                 vec.iov_base   = dummy;
    00343                 vec.iov_len    = sizeof(dummy);
    00344                 msg.msg_iov    = &vec;
    00345                 msg.msg_iovlen = 1;
    00346
    00347                 msg.msg_control    = (caddr_t) &control_data;
    00348                 msg.msg_controllen = sizeof(control_data);
    00349                 msg.msg_flags      = 0;
    00350
    00351                 control_header = CMSG_FIRSTHDR(&msg);
    00352                 control_header->cmsg_level = SOL_SOCKET;
    00353                 control_header->cmsg_type  = SCM_RIGHTS;
    00354                 #if defined(__APPLE__) || defined(__SOLARIS__)
    00355                         control_header->cmsg_len = sizeof(control_data);
    00356                         control_data.fd = fileDescriptor;
    00357                 #else
    00358                         control_header->cmsg_len = CMSG_LEN(sizeof(int));
    00359                         memcpy(CMSG_DATA(control_header), &fileDescriptor, sizeof(int));
    00360                 #endif
    00361
    00362                 ret = syscalls::sendmsg(fd, &msg, 0);
    00363                 if (ret == -1) {
    00364                         throw SystemException("Cannot send file descriptor with sendmsg()", errno);
    00365                 }
    00366         }
    00367
    00368         /**
    00369          * Read an array message from the underlying file descriptor.
    00370          *
    00371          * @param args The message will be put in this variable.
    00372          * @return Whether end-of-file has been reached. If so, then the contents
    00373          *         of <tt>args</tt> will be undefined.
    00374          * @throws SystemException If an error occured while receiving the message.
    00375          * @throws boost::thread_interrupted
    00376          * @see write()
    00377          */
    00378         bool read(vector<string> &args) {
    00379                 uint16_t size;
    00380                 int ret;
    00381                 unsigned int alreadyRead = 0;
    00382
    00383                 do {
    00384                         ret = syscalls::read(fd, (char *) &size + alreadyRead, sizeof(size) - alreadyRead);
    00385                         if (ret == -1) {
    00386                                 throw SystemException("read() failed", errno);
    00387                         } else if (ret == 0) {
    00388                                 return false;
    00389                         }
    00390                         alreadyRead += ret;
    00391                 } while (alreadyRead < sizeof(size));
    00392                 size = ntohs(size);
    00393
    00394                 string buffer;
    00395                 args.clear();
    00396                 buffer.reserve(size);
    00397                 while (buffer.size() < size) {
    00398                         char tmp[1024 * 8];
    00399                         ret = syscalls::read(fd, tmp, min(size - buffer.size(), sizeof(tmp)));
    00400                         if (ret == -1) {
    00401                                 throw SystemException("read() failed", errno);
    00402                         } else if (ret == 0) {
    00403                                 return false;
    00404                         }
    00405                         buffer.append(tmp, ret);
    00406                 }
    00407
    00408                 if (!buffer.empty()) {
    00409                         string::size_type start = 0, pos;
    00410                         const string &const_buffer(buffer);
    00411                         while ((pos = const_buffer.find('\0', start)) != string::npos) {
    00412                                 args.push_back(const_buffer.substr(start, pos - start));
    00413                                 start = pos + 1;
    00414                         }
    00415                 }
    00416                 return true;
    00417         }
    00418
    00419         /**
    00420          * Read a scalar message from the underlying file descriptor.
    00421          *
    00422          * @param output The message will be put in here.
    00423          * @returns Whether end-of-file was reached during reading.
    00424          * @throws SystemException An error occured while writing the data to the file descriptor.
    00425          * @throws boost::thread_interrupted
    00426          * @see writeScalar()
    00427          */
    00428         bool readScalar(string &output) {
    00429                 uint32_t size;
    00430                 unsigned int remaining;
    00431
    00432                 if (!readRaw(&size, sizeof(uint32_t))) {
    00433                         return false;
    00434                 }
    00435                 size = ntohl(size);
    00436
    00437                 output.clear();
    00438                 output.reserve(size);
    00439                 remaining = size;
    00440                 while (remaining > 0) {
    00441                         char buf[1024 * 32];
    00442                         unsigned int blockSize = min((unsigned int) sizeof(buf), remaining);
    00443
    00444                         if (!readRaw(buf, blockSize)) {
    00445                                 return false;
    00446                         }
    00447                         output.append(buf, blockSize);
    00448                         remaining -= blockSize;
    00449                 }
    00450                 return true;
    00451         }
    00452
    00453         /**
    00454          * Read exactly <tt>size</tt> bytes of data from the underlying file descriptor,
    00455          * and put the result in <tt>buf</tt>. If end-of-file has been reached, or if
    00456          * end-of-file was encountered before <tt>size</tt> bytes have been read, then
    00457          * <tt>false</tt> will be returned. Otherwise (i.e. if the read was successful),
    00458          * <tt>true</tt> will be returned.
    00459          *
    00460          * @param buf The buffer to place the read data in. This buffer must be at least
    00461          *            <tt>size</tt> bytes long.
    00462          * @param size The number of bytes to read.
    00463          * @return Whether reading was successful or whether EOF was reached.
    00464          * @pre buf != NULL
    00465          * @throws SystemException Something went wrong during reading.
    00466          * @throws boost::thread_interrupted
    00467          * @see writeRaw()
    00468          */
    00469         bool readRaw(void *buf, unsigned int size) {
    00470                 ssize_t ret;
    00471                 unsigned int alreadyRead = 0;
    00472
    00473                 while (alreadyRead < size) {
    00474                         ret = syscalls::read(fd, (char *) buf + alreadyRead, size - alreadyRead);
    00475                         if (ret == -1) {
    00476                                 throw SystemException("read() failed", errno);
    00477                         } else if (ret == 0) {
    00478                                 return false;
    00479                         } else {
    00480                                 alreadyRead += ret;
    00481                         }
    00482                 }
    00483                 return true;
    00484         }
    00485
    00486         /**
    00487          * Receive a file descriptor, which had been passed over the underlying
    00488          * file descriptor.
    00489          *
    00490          * @return The passed file descriptor.
    00491          * @throws SystemException If something went wrong during the
    00492          *            receiving of a file descriptor. Perhaps the underlying
    00493          *            file descriptor isn't a Unix socket.
    00494          * @throws IOException Whatever was received doesn't seem to be a
    00495          *            file descriptor.
    00496          * @throws boost::thread_interrupted
    00497          */
    00498         int readFileDescriptor() {
    00499                 struct msghdr msg;
    00500                 struct iovec vec;
    00501                 char dummy[1];
    00502                 #if defined(__APPLE__) || defined(__SOLARIS__)
    00503                         // File descriptor passing macros (CMSG_*) seem to be broken
    00504                         // on 64-bit MacOS X. This structure works around the problem.
    00505                         struct {
    00506                                 struct cmsghdr header;
    00507                                 int fd;
    00508                         } control_data;
    00509                         #define EXPECTED_CMSG_LEN sizeof(control_data)
    00510                 #else
    00511                         char control_data[CMSG_SPACE(sizeof(int))];
    00512                         #define EXPECTED_CMSG_LEN CMSG_LEN(sizeof(int))
    00513                 #endif
    00514                 struct cmsghdr *control_header;
    00515                 int ret;
    00516
    00517                 msg.msg_name    = NULL;
    00518                 msg.msg_namelen = 0;
    00519
    00520                 dummy[0]       = '\0';
    00521                 vec.iov_base   = dummy;
    00522                 vec.iov_len    = sizeof(dummy);
    00523                 msg.msg_iov    = &vec;
    00524                 msg.msg_iovlen = 1;
    00525
    00526                 msg.msg_control    = (caddr_t) &control_data;
    00527                 msg.msg_controllen = sizeof(control_data);
    00528                 msg.msg_flags      = 0;
    00529
    00530                 ret = syscalls::recvmsg(fd, &msg, 0);
    00531                 if (ret == -1) {
    00532                         throw SystemException("Cannot read file descriptor with recvmsg()", errno);
    00533                 }
    00534
    00535                 control_header = CMSG_FIRSTHDR(&msg);
    00536                 if (control_header->cmsg_len   != EXPECTED_CMSG_LEN
    00537                  || control_header->cmsg_level != SOL_SOCKET
    00538                  || control_header->cmsg_type  != SCM_RIGHTS) {
    00539                         throw IOException("No valid file descriptor received.");
    00540                 }
    00541                 #if defined(__APPLE__) || defined(__SOLARIS__)
    00542                         return control_data.fd;
    00543                 #else
    00544                         return *((int *) CMSG_DATA(control_header));
    00545                 #endif
    00546         }
    00547
    00548         /**
    00549          * Set the timeout value for reading data from this channel.
    00550          * If no data can be read within the timeout period, then a
    00551          * SystemException will be thrown by one of the read methods,
    00552          * with error code EAGAIN or EWOULDBLOCK.
    00553          *
    00554          * @param msec The timeout, in milliseconds. If 0 is given,
    00555          *             there will be no timeout.
    00556          * @throws SystemException Cannot set the timeout.
    00557          */
    00558         void setReadTimeout(unsigned int msec) {
    00559                 // See the comment for setWriteTimeout().
    00560                 struct timeval tv;
    00561                 int ret;
    00562
    00563                 tv.tv_sec = msec / 1000;
    00564                 tv.tv_usec = msec % 1000 * 1000;
    00565                 ret = syscalls::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
    00566                         &tv, sizeof(tv));
    00567                 #ifndef __SOLARIS__
    00568                 // SO_RCVTIMEO is unimplemented and retuns an error on Solaris
    00569                 // 9 and 10 SPARC.  Seems to work okay without it.
    00570                 if (ret == -1) {
    00571                         throw SystemException("Cannot set read timeout for socket", errno);
    00572                 }
    00573                 #endif
    00574         }
    00575
    00576         /**
    00577          * Set the timeout value for writing data to this channel.
    00578          * If no data can be written within the timeout period, then a
    00579          * SystemException will be thrown, with error code EAGAIN or
    00580          * EWOULDBLOCK.
    00581          *
    00582          * @param msec The timeout, in milliseconds. If 0 is given,
    00583          *             there will be no timeout.
    00584          * @throws SystemException Cannot set the timeout.
    00585          */
    00586         void setWriteTimeout(unsigned int msec) {
    00587                 // People say that SO_RCVTIMEO/SO_SNDTIMEO are unreliable and
    00588                 // not well-implemented on all platforms.
    00589                 // http://www.developerweb.net/forum/archive/index.php/t-3439.html
    00590                 // That's why we use APR's timeout facilities as well (see Hooks.cpp).
    00591                 struct timeval tv;
    00592                 int ret;
    00593
    00594                 tv.tv_sec = msec / 1000;
    00595                 tv.tv_usec = msec % 1000 * 1000;
    00596                 ret = syscalls::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO,
    00597                         &tv, sizeof(tv));
    00598                 #ifndef __SOLARIS__
    00599                 // SO_SNDTIMEO is unimplemented and returns an error on Solaris
    00600                 // 9 and 10 SPARC.  Seems to work okay without it.
    00601                 if (ret == -1) {
    00602                         throw SystemException("Cannot set read timeout for socket", errno);
    00603                 }
    00604                 #endif
    00605         }
    00606 };
    00607
    00608 } // namespace Passenger
    00609
    00610 #endif /* _PASSENGER_MESSAGE_CHANNEL_H_ */