We got nominated! Help us out and vote for GitHub as Best Bootstrapped Startup of 2008. (You can vote once a day.) [ hide ]

public
Description: Phusion Passenger (mod_rails)
Homepage: http://www.modrails.com/
Clone URL: git://github.com/FooBarWidget/passenger.git
Click here to lend your support to: passenger and make a donation at www.pledgie.com !
- Improve documentation for the MessageChannel and SpawnManager C++ 
classes.
- Make sure a 500 Internal Server Error is returned if an unexpected 
exception is caught during a HTTP request.
- Make the spawn server print its errors to the Apache error log file 
instead of its own log file.
FooBarWidget (author)
Fri Feb 01 07:30:50 -0800 2008
commit  42b311bb9b1137922bb9d873d58cc6e7672886c2
tree    c0dd0d073f596a8f74472cab1e3d2f43d0a88ebe
parent  86a183863f623e835fb54c615dda3ef9edd1386e
...
217
218
219
220
221
 
222
223
224
...
229
230
231
232
233
234
235
...
254
255
256
257
258
259
260
261
...
280
281
282
 
 
283
284
 
285
286
287
288
289
...
217
218
219
 
 
220
221
222
223
...
228
229
230
 
231
232
233
...
252
253
254
 
 
255
256
257
...
276
277
278
279
280
281
282
283
284
 
285
286
287
0
@@ -217,8 +217,7 @@ public:
0
     ap_add_version_component(pconf, "Phusion_Passenger/" PASSENGER_VERSION);
0
     
0
     const char *spawnManagerCommand = "/home/hongli/Projects/mod_rails/lib/mod_rails/spawn_manager.rb";
0
- const char *logFile = "/home/hongli/Projects/mod_rails/spawner_log.txt";
0
- applicationPool = ApplicationPoolPtr(new ApplicationPool(spawnManagerCommand, logFile, "production"));
0
+ applicationPool = ApplicationPoolPtr(new ApplicationPool(spawnManagerCommand, "", "production"));
0
   }
0
   
0
   ~Hooks() {
0
@@ -229,7 +228,6 @@ public:
0
   }
0
   
0
   int handleRequest(request_rec *r) {
0
- // The main request handler hook function.
0
     RailsConfig *config = getConfig(r);
0
     const char *railsDir;
0
     
0
@@ -254,8 +252,6 @@ public:
0
       return OK;
0
     }
0
     
0
-
0
-
0
     /* int httpStatus = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
0
         if (httpStatus != OK) {
0
       return httpStatus;
0
@@ -280,10 +276,12 @@ public:
0
 
0
       ap_scan_script_header_err_brigade(r, bb, NULL);
0
       ap_pass_brigade(r->output_filters, bb);
0
+
0
+ return OK;
0
     } catch (const exception &e) {
0
       ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "mod_passenger unknown error: %s", e.what());
0
+ return HTTP_INTERNAL_SERVER_ERROR;
0
     }
0
- return OK;
0
   }
0
   
0
   int
...
18
19
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
22
23
24
25
26
 
 
 
 
 
 
27
28
29
30
 
 
 
31
32
33
34
 
 
 
 
35
36
37
...
39
40
41
 
 
 
 
 
 
42
43
44
...
70
71
72
 
 
 
 
 
 
 
 
 
73
74
75
...
88
89
90
 
 
 
 
 
 
 
 
91
92
93
...
120
121
122
 
 
 
 
 
 
 
 
123
124
125
...
165
166
167
 
 
 
 
 
 
 
 
 
 
 
168
169
170
...
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
...
83
84
85
86
87
88
89
90
91
92
93
94
...
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
...
147
148
149
150
151
152
153
154
155
156
157
158
159
160
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
...
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
0
@@ -18,20 +18,64 @@ namespace Passenger {
0
 
0
 using namespace std;
0
 
0
+/**
0
+ * This class provides convenience methods for:
0
+ * - sending and receiving discrete messages over a file descriptor.
0
+ * A message is just a list of strings.
0
+ * - file descriptor passing over a Unix socket.
0
+ *
0
+ * MessageChannel is to be wrapped around a file descriptor. For example:
0
+ * @code
0
+ * int p[2];
0
+ * pipe(p);
0
+ * MessageChannel channel1(p[0]);
0
+ * MessageChannel channel2(p[1]);
0
+ *
0
+ * channel2.write("hello", "world !!", NULL);
0
+ * list<string> args;
0
+ * channel1.read(args); // args now contains { "hello", "world !!" }
0
+ * @endcode
0
+ *
0
+ * The life time of a MessageChannel is independent from that of the
0
+ * wrapped file descriptor. If a MessageChannel object is destroyed,
0
+ * the file descriptor is not automatically closed. Call close()
0
+ * if you want to close the file descriptor.
0
+ *
0
+ * @note I/O operations are not buffered.
0
+ * @note Be careful with mixing the sending/receiving of messages file
0
+ * descriptor passing. These operations have stream properties.
0
+ * Suppose you first send a message, then pass a file descriptor.
0
+ * If the other side of the communication channel first tries to
0
+ * receive a file descriptor, and then tries to receive a message,
0
+ * then bad things will happen.
0
+ */
0
 class MessageChannel {
0
 private:
0
   const static char DELIMITER = '\0';
0
   int fd;
0
 
0
 public:
0
+ /**
0
+ * Construct a new MessageChannel with no underlying file descriptor.
0
+ * Thus the resulting MessageChannel object will not be usable.
0
+ * This constructor exists to allow one to declare an "empty"
0
+ * MessageChannel variable which is to be initialized later.
0
+ */
0
   MessageChannel() {
0
     this->fd = -1;
0
   }
0
 
0
+ /**
0
+ * Construct a new MessageChannel with the given file descriptor.
0
+ */
0
   MessageChannel(int fd) {
0
     this->fd = fd;
0
   }
0
   
0
+ /**
0
+ * Close the underlying file descriptor. If this method is called multiple
0
+ * times, the file descriptor will only be closed the first time.
0
+ */
0
   void close() {
0
     if (fd != -1) {
0
       ::close(fd);
0
@@ -39,6 +83,12 @@ public:
0
     }
0
   }
0
 
0
+ /**
0
+ * Send the message, which consists of the given elements, over the underlying
0
+ * file descriptor.
0
+ *
0
+ * @throws SystemException An error occured while writing the data to the file descriptor.
0
+ */
0
   void write(const list<string> &args) {
0
     list<string>::const_iterator it;
0
     string data;
0
@@ -70,6 +120,15 @@ public:
0
     } while (written < data.size());
0
   }
0
   
0
+ /**
0
+ * Send the message, which consists of the given strings, over the underlying
0
+ * file descriptor.
0
+ *
0
+ * @param name The first element of the message to send.
0
+ * @param ... Other elements of the message. These *must* be strings, i.e. of type char*.
0
+ * It is also required to terminate this list with a NULL.
0
+ * @throws SystemException An error occured while writing the data to the file descriptor.
0
+ */
0
   void write(const char *name, ...) {
0
     list<string> args;
0
     args.push_back(name);
0
@@ -88,6 +147,14 @@ public:
0
     write(args);
0
   }
0
   
0
+ /**
0
+ * Pass a file descriptor. This only works if the underlying file
0
+ * descriptor is a Unix socket.
0
+ *
0
+ * @param fileDescriptor The file descriptor to pass.
0
+ * @throws SystemException Something went wrong during file descriptor passing.
0
+ * @pre <tt>fileDescriptor >= 0</tt>
0
+ */
0
   void writeFileDescriptor(int fileDescriptor) {
0
     struct msghdr msg;
0
     struct iovec vec[1];
0
@@ -120,6 +187,14 @@ public:
0
     }
0
   }
0
   
0
+ /**
0
+ * Receive a message from the underlying file descriptor.
0
+ *
0
+ * @param args The message will be put in this variable.
0
+ * @return Whether end-of-file has been reached. If so, then the contents
0
+ * of <tt>args</tt> will be undefined.
0
+ * @throws SystemException If an error occured while receiving the message.
0
+ */
0
   bool read(vector<string> &args) {
0
     uint16_t size;
0
     int ret;
0
@@ -165,6 +240,17 @@ public:
0
     return true;
0
   }
0
   
0
+ /**
0
+ * Receive a file descriptor, which had been passed over the underlying
0
+ * file descriptor.
0
+ *
0
+ * @return The passed file descriptor.
0
+ * @throws SystemException If something went wrong during the
0
+ * receiving of a file descriptor. Perhaps the underlying
0
+ * file descriptor isn't a Unix socket.
0
+ * @throws IOException Whatever was received doesn't seem to be a
0
+ * file descriptor.
0
+ */
0
   int readFileDescriptor() {
0
     struct msghdr msg;
0
     struct iovec vec[2];
...
23
24
25
26
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
29
30
...
32
33
34
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
...
58
59
60
61
62
63
64
65
66
67
68
 
69
70
71
 
72
73
 
 
 
 
 
 
 
 
 
 
74
75
76
...
82
83
84
 
 
 
85
86
87
...
93
94
95
96
 
97
98
99
...
23
24
25
 
 
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
51
52
53
 
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
 
76
77
78
79
80
 
 
 
81
82
83
...
90
91
92
 
93
94
95
 
96
97
98
99
100
101
102
103
104
 
105
106
107
108
109
110
111
112
113
114
115
116
117
...
123
124
125
126
127
128
129
130
131
...
137
138
139
 
140
141
142
143
0
@@ -23,8 +23,27 @@ using namespace std;
0
 using namespace boost;
0
 
0
 /**
0
- * This class is responsible for spawning Ruby on Rails applications.
0
- * TODO: write better documentation
0
+ * This class is responsible for spawning new instances of Ruby on Rails applications.
0
+ * Use the spawn() method to do so.
0
+ *
0
+ * <h2>Implementation details</h2>
0
+ * Internally, it makes use of a spawn server, which is written in Ruby. This server
0
+ * is automatically started when a SpawnManager instance is created, and automatically
0
+ * shutdown when that instance is destroyed. Spawning requests are sent to the server,
0
+ * and details about the spawned process is returned.
0
+ *
0
+ * The communication channel with the server is anonymous, i.e. no other processes
0
+ * can access the communication channel, so communication is guaranteed to be safe
0
+ * (unless, of course, if the spawn server itself is a trojan).
0
+ *
0
+ * The server will try to keep the spawning time as small as possible, by keeping
0
+ * corresponding Ruby on Rails frameworks and application code in memory. So the second
0
+ * time an instance of the same application is spawned, the spawn time is significantly
0
+ * lower than the first time. Nevertheless, spawning is a relatively expensive operation
0
+ * (compared to the processing of a typical HTTP request/response), and so should be
0
+ * avoided whenever possible.
0
+ *
0
+ * See the documentation of the spawn server for full implementation details.
0
  */
0
 class SpawnManager {
0
 private:
0
@@ -32,20 +51,33 @@ private:
0
   pid_t pid;
0
 
0
 public:
0
- SpawnManager(const string &spawnManagerCommand,
0
+ /**
0
+ * Construct a new SpawnManager.
0
+ *
0
+ * @param spawnServerCommand The filename of the spawn server to use.
0
+ * @param logFile Specify a log file that the spawn manager should use.
0
+ * Messages on its standard output and standard error channels
0
+ * will be written to this log file. If an empty string is
0
+ * specified, no log file will be used, and the spawn server
0
+ * will use the same standard output/error channels as the
0
+ * current process.
0
+ * @param environment The RAILS_ENV environment that all RoR applications
0
+ * should use. If an empty string is specified, the current value
0
+ * of the RAILS_ENV environment variable will be used.
0
+ * @param rubyCommand The Ruby interpreter's command.
0
+ * @param SystemException An error occured while trying to setup the spawn server.
0
+ * @throws IOException The specified log file could not be opened.
0
+ */
0
+ SpawnManager(const string &spawnServerCommand,
0
    const string &logFile = "",
0
    const string &environment = "production",
0
    const string &rubyCommand = "ruby") {
0
     int fds[2];
0
- char fd_string[20];
0
     FILE *logFileHandle = NULL;
0
     
0
     if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
0
       throw SystemException("Cannot create a Unix socket", errno);
0
     }
0
- if (apr_snprintf(fd_string, sizeof(fd_string), "%d", fds[1]) <= 0) {
0
- throw MemoryException();
0
- }
0
     if (!logFile.empty()) {
0
       logFileHandle = fopen(logFile.c_str(), "a");
0
       if (logFileHandle == NULL) {
0
@@ -58,19 +90,28 @@ public:
0
 
0
     pid = fork();
0
     if (pid == 0) {
0
- // TODO: redirect stderr to a log file
0
       pid = fork();
0
       if (pid == 0) {
0
         if (!logFile.empty()) {
0
- dup2(fileno(logFileHandle), STDOUT_FILENO);
0
           dup2(fileno(logFileHandle), STDERR_FILENO);
0
           fclose(logFileHandle);
0
         }
0
+ dup2(STDERR_FILENO, STDOUT_FILENO);
0
         if (!environment.empty()) {
0
           setenv("RAILS_ENV", environment.c_str(), true);
0
         }
0
+ dup2(fds[1], STDIN_FILENO);
0
         close(fds[0]);
0
- execlp(rubyCommand.c_str(), rubyCommand.c_str(), spawnManagerCommand.c_str(), fd_string, NULL);
0
+ close(fds[1]);
0
+
0
+ // Close all other file descriptors
0
+ for (int i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
0
+ if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) {
0
+ close(i);
0
+ }
0
+ }
0
+
0
+ execlp(rubyCommand.c_str(), rubyCommand.c_str(), spawnServerCommand.c_str(), NULL);
0
         fprintf(stderr, "Unable to run %s: %s\n", rubyCommand.c_str(), strerror(errno));
0
         _exit(1);
0
       } else if (pid == -1) {
0
@@ -82,6 +123,9 @@ public:
0
     } else if (pid == -1) {
0
       close(fds[0]);
0
       close(fds[1]);
0
+ if (logFileHandle != NULL) {
0
+ fclose(logFileHandle);
0
+ }
0
       throw SystemException("Unable to fork a process", errno);
0
     } else {
0
       close(fds[1]);
0
@@ -93,7 +137,7 @@ public:
0
     }
0
   }
0
   
0
- ~SpawnManager() {
0
+ ~SpawnManager() throw() {
0
     channel.close();
0
   }
0
   
...
162
163
164
165
166
167
 
168
169
170
...
162
163
164
 
165
 
166
167
168
169
0
@@ -162,9 +162,8 @@ private
0
 end
0
 
0
 if __FILE__ == $0
0
- unix_socket = IO.new(ARGV[0].to_i, "a+")
0
   spawn_manager = SpawnManager.new
0
- spawn_manager.server_main(unix_socket)
0
+ spawn_manager.server_main(IO.new(0, "a+"))
0
   spawn_manager.cleanup
0
 end
0
 

Comments

    No one has commented yet.