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 !
Implement conservative spawning support in SpawnManager.
Hongli Lai (Phusion) (author)
Wed May 07 13:15:58 -0700 2008
commit  f54959bf1a53f78ef17bb5b3cb5cd31e1af9952e
tree    20aba2e902a203ca32ee9413c669a7e39d62d3de
parent  7e8593a28a91c2c471878910a1d80d09103a9ae0
...
106
107
108
 
 
109
110
111
...
116
117
118
119
 
 
120
121
122
...
106
107
108
109
110
111
112
113
...
118
119
120
 
121
122
123
124
125
0
@@ -106,6 +106,8 @@ public:
0
    * @param lowerPrivilege Whether to lower the application's privileges.
0
    * @param lowestUser The user to fallback to if lowering privilege fails.
0
    * @param environment The RAILS_ENV environment that should be used. May not be empty.
0
+ * @param spawnMethod The spawn method to use. Either "smart" or "conservative".
0
+ * See the Ruby class SpawnManager for details.
0
    * @return A session object.
0
    * @throw SpawnException An attempt was made to spawn a new application instance, but that attempt failed.
0
    * @throw IOException Something else went wrong.
0
@@ -116,7 +118,8 @@ public:
0
    * they're 2 different applications, and thus will spawn 2 application instances.
0
    */
0
   virtual Application::SessionPtr get(const string &appRoot, bool lowerPrivilege = true,
0
- const string &lowestUser = "nobody", const string &environment = "production") = 0;
0
+ const string &lowestUser = "nobody", const string &environment = "production",
0
+ const string &spawnMethod = "smart") = 0;
0
   
0
   /**
0
    * Clear all application instances that are currently in the pool.
...
260
261
262
263
 
 
264
265
266
...
271
272
273
 
274
275
276
...
260
261
262
 
263
264
265
266
267
...
272
273
274
275
276
277
278
0
@@ -260,7 +260,8 @@ private:
0
       const string &appRoot,
0
       bool lowerPrivilege = true,
0
       const string &lowestUser = "nobody",
0
- const string &environment = "production"
0
+ const string &environment = "production",
0
+ const string &spawnMethod = "smart"
0
     ) {
0
       MessageChannel channel(data->server);
0
       vector<string> args;
0
@@ -271,6 +272,7 @@ private:
0
           (lowerPrivilege) ? "true" : "false",
0
           lowestUser.c_str(),
0
           environment.c_str(),
0
+ spawnMethod.c_str(),
0
           NULL);
0
       } catch (const SystemException &) {
0
         throw IOException("The ApplicationPool server exited unexpectedly.");
...
147
148
149
150
 
151
152
153
...
236
237
238
239
 
240
241
242
...
147
148
149
 
150
151
152
153
...
236
237
238
 
239
240
241
242
0
@@ -147,7 +147,7 @@ private:
0
     bool failed = false;
0
     
0
     try {
0
- session = server.pool.get(args[1], args[2] == "true", args[3], args[4]);
0
+ session = server.pool.get(args[1], args[2] == "true", args[3], args[4], args[5]);
0
       sessions[lastSessionID] = session;
0
       lastSessionID++;
0
     } catch (const SpawnException &e) {
0
@@ -236,7 +236,7 @@ private:
0
         
0
         P_TRACE(3, "Client " << this << ": received message: " <<
0
           toString(args));
0
- if (args[0] == "get" && args.size() == 5) {
0
+ if (args[0] == "get" && args.size() == 6) {
0
           processGet(args);
0
         } else if (args[0] == "close" && args.size() == 2) {
0
           processClose(args);
...
237
238
239
 
240
241
242
...
244
245
246
247
 
 
248
249
250
...
255
256
257
 
258
259
260
...
320
321
322
323
 
324
325
326
...
335
336
337
338
 
 
339
340
341
...
470
471
472
 
 
473
474
475
...
479
480
481
482
 
 
483
484
485
486
 
 
487
488
489
490
491
492
 
493
494
495
...
237
238
239
240
241
242
243
...
245
246
247
 
248
249
250
251
252
...
257
258
259
260
261
262
263
...
323
324
325
 
326
327
328
329
...
338
339
340
 
341
342
343
344
345
...
474
475
476
477
478
479
480
481
...
485
486
487
 
488
489
490
491
492
 
493
494
495
496
497
498
499
 
500
501
502
503
0
@@ -237,6 +237,7 @@ private:
0
    * @param lowerPrivilege Whether to lower the application's privileges.
0
    * @param lowestUser The user to fallback to if lowering privilege fails.
0
    * @param environment The RAILS_ENV environment that should be used.
0
+ * @param spawnMethod The spawn method to use.
0
    * @return An Application smart pointer, representing the spawned application.
0
    * @throws SpawnException Something went wrong.
0
    */
0
@@ -244,7 +245,8 @@ private:
0
     const string &appRoot,
0
     bool lowerPrivilege,
0
     const string &lowestUser,
0
- const string &environment
0
+ const string &environment,
0
+ const string &spawnMethod
0
   ) {
0
     vector<string> args;
0
     int ownerPipe;
0
@@ -255,6 +257,7 @@ private:
0
         (lowerPrivilege) ? "true" : "false",
0
         lowestUser.c_str(),
0
         environment.c_str(),
0
+ spawnMethod.c_str(),
0
         NULL);
0
     } catch (const SystemException &e) {
0
       throw SpawnException(string("Could not write 'spawn_application' "
0
@@ -320,7 +323,7 @@ private:
0
   ApplicationPtr
0
   handleSpawnException(const SpawnException &e, const string &appRoot,
0
    bool lowerPrivilege, const string &lowestUser,
0
- const string &environment) {
0
+ const string &environment, const string &spawnMethod) {
0
     bool restarted;
0
     try {
0
       P_DEBUG("Spawn server died. Attempting to restart it...");
0
@@ -335,7 +338,8 @@ private:
0
       restarted = false;
0
     }
0
     if (restarted) {
0
- return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser, environment);
0
+ return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser,
0
+ environment, spawnMethod);
0
     } else {
0
       throw SpawnException("The spawn server died unexpectedly, and restarting it failed.");
0
     }
0
@@ -470,6 +474,8 @@ public:
0
    * @param lowerPrivilege Whether to lower the application's privileges.
0
    * @param lowestUser The user to fallback to if lowering privilege fails.
0
     * @param environment The RAILS_ENV environment that should be used. May not be empty.
0
+ * @param spawnMethod The spawn method to use. Either "smart" or "conservative".
0
+ * See the Ruby class SpawnManager for details.
0
    * @return A smart pointer to an Application object, which represents the application
0
    * instance that has been spawned. Use this object to communicate with the
0
    * spawned application.
0
@@ -479,17 +485,19 @@ public:
0
     const string &appRoot,
0
     bool lowerPrivilege = true,
0
     const string &lowestUser = "nobody",
0
- const string &environment = "production"
0
+ const string &environment = "production",
0
+ const string &spawnMethod = "smart"
0
   ) {
0
     mutex::scoped_lock l(lock);
0
     try {
0
- return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser, environment);
0
+ return sendSpawnCommand(appRoot, lowerPrivilege, lowestUser,
0
+ environment, spawnMethod);
0
     } catch (const SpawnException &e) {
0
       if (e.hasErrorPage()) {
0
         throw;
0
       } else {
0
         return handleSpawnException(e, appRoot, lowerPrivilege,
0
- lowestUser, environment);
0
+ lowestUser, environment, spawnMethod);
0
       }
0
     }
0
   }
...
274
275
276
277
 
 
278
279
280
...
317
318
319
320
 
 
321
322
323
...
343
344
345
346
 
347
348
349
...
450
451
452
453
 
 
454
455
456
...
461
462
463
464
 
 
465
466
467
...
274
275
276
 
277
278
279
280
281
...
318
319
320
 
321
322
323
324
325
...
345
346
347
 
348
349
350
351
...
452
453
454
 
455
456
457
458
459
...
464
465
466
 
467
468
469
470
471
0
@@ -274,7 +274,8 @@ private:
0
     const string &appRoot,
0
     bool lowerPrivilege,
0
     const string &lowestUser,
0
- const string &environment
0
+ const string &environment,
0
+ const string &spawnMethod
0
   ) {
0
     AppContainerPtr container;
0
     AppContainerList *list;
0
@@ -317,7 +318,8 @@ private:
0
         } else {
0
           container = ptr(new AppContainer());
0
           container->app = spawnManager.spawn(appRoot,
0
- lowerPrivilege, lowestUser, environment);
0
+ lowerPrivilege, lowestUser, environment,
0
+ spawnMethod);
0
           container->sessions = 0;
0
           list->push_back(container);
0
           container->iterator = list->end();
0
@@ -343,7 +345,7 @@ private:
0
         }
0
         container = ptr(new AppContainer());
0
         container->app = spawnManager.spawn(appRoot, lowerPrivilege, lowestUser,
0
- environment);
0
+ environment, spawnMethod);
0
         container->sessions = 0;
0
         it = apps.find(appRoot);
0
         if (it == apps.end()) {
0
@@ -450,7 +452,8 @@ public:
0
     const string &appRoot,
0
     bool lowerPrivilege = true,
0
     const string &lowestUser = "nobody",
0
- const string &environment = "production"
0
+ const string &environment = "production",
0
+ const string &spawnMethod = "smart"
0
   ) {
0
     unsigned int attempt;
0
     const unsigned int MAX_ATTEMPTS = 5;
0
@@ -461,7 +464,8 @@ public:
0
       
0
       mutex::scoped_lock l(lock);
0
       pair<AppContainerPtr, AppContainerList *> p(
0
- spawnOrUseExisting(l, appRoot, lowerPrivilege, lowestUser, environment)
0
+ spawnOrUseExisting(l, appRoot, lowerPrivilege, lowestUser,
0
+ environment, spawnMethod)
0
       );
0
       AppContainerPtr &container(p.first);
0
       AppContainerList &list(*p.second);
...
173
174
175
176
 
177
178
179
...
173
174
175
 
176
177
178
179
0
@@ -173,7 +173,7 @@ class AbstractServer
0
     @parent_socket.close
0
     @parent_channel = nil
0
     
0
- # Wait at most 5 seconds for server to exit. If it doesn't do that,
0
+ # Wait at most 3 seconds for server to exit. If it doesn't do that,
0
     # we kill it. If that doesn't work either, we kill it forcefully with
0
     # SIGKILL.
0
     begin
...
180
181
182
183
 
184
185
186
...
180
181
182
 
183
184
185
186
0
@@ -180,7 +180,7 @@ class ApplicationSpawner < AbstractServer
0
       end
0
     end
0
     b.close
0
- Process.waitpid(pid)
0
+ Process.waitpid(pid) rescue nil
0
     
0
     channel = MessageChannel.new(a)
0
     unmarshal_and_raise_errors(channel)
...
64
65
66
67
68
69
 
 
 
 
70
71
 
 
 
 
72
73
74
...
77
78
79
80
81
82
83
84
85
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
88
 
89
90
91
92
93
94
95
96
97
98
99
100
101
...
111
112
113
114
 
115
 
 
116
117
118
...
176
177
178
179
 
180
181
182
183
 
 
184
185
186
...
64
65
66
 
 
 
67
68
69
70
71
72
73
74
75
76
77
78
79
...
82
83
84
 
 
 
 
 
 
 
85
86
87
88
89
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
...
125
126
127
 
128
129
130
131
132
133
134
...
192
193
194
 
195
196
197
198
 
199
200
201
202
203
0
@@ -64,11 +64,16 @@ class SpawnManager < AbstractServer
0
   # See ApplicationSpawner.new for an explanation of the +lower_privilege+,
0
   # +lowest_user+ and +environment+ parameters.
0
   #
0
- # SpawnManager will internally cache the code of applications, in order to
0
- # speed up future spawning attempts. This implies that, if you've
0
- # changed the application's code, you must do one of these things:
0
+ # The +spawn_method+ argument may be one of "smart" or "conservative".
0
+ # When "smart" is specified (the default), SpawnManager will internally cache the
0
+ # code of applications, in order to speed up future spawning attempts. This implies
0
+ # that, if you've changed the application's code, you must do one of these things:
0
   # - Restart this SpawnManager by calling AbstractServer#stop, then AbstractServer#start.
0
   # - Reload the application by calling reload with the correct app_root argument.
0
+ # Caching however can be incompatible with some applications.
0
+ #
0
+ # The "conservative" spawning method does not involve any caching at all.
0
+ # Spawning will be slower, but is guaranteed to be compatible with all applications.
0
   #
0
   # Raises:
0
   # - ArgumentError: +app_root+ doesn't appear to be a valid Ruby on Rails application root.
0
@@ -77,25 +82,34 @@ class SpawnManager < AbstractServer
0
   # - AbstractServer::ServerError: One of the server processes exited unexpectedly.
0
   # - FrameworkInitError: The Ruby on Rails framework that the application requires could not be loaded.
0
   # - AppInitError: The application raised an exception or called exit() during startup.
0
- def spawn_application(app_root, lower_privilege = true, lowest_user = "nobody", environment = "production")
0
- framework_version = Application.detect_framework_version(app_root)
0
- if framework_version == :vendor
0
- vendor_path = normalize_path("#{app_root}/vendor/rails")
0
- key = "vendor:#{vendor_path}"
0
- create_spawner = proc do
0
- FrameworkSpawner.new(:vendor => vendor_path)
0
+ def spawn_application(app_root, lower_privilege = true, lowest_user = "nobody",
0
+ environment = "production", spawn_method = "smart")
0
+ if spawn_method == "smart"
0
+ framework_version = Application.detect_framework_version(app_root)
0
+ if framework_version == :vendor
0
+ vendor_path = normalize_path("#{app_root}/vendor/rails")
0
+ key = "vendor:#{vendor_path}"
0
+ create_spawner = proc do
0
+ FrameworkSpawner.new(:vendor => vendor_path)
0
+ end
0
+ elsif framework_version.nil?
0
+ app_root = normalize_path(app_root)
0
+ key = "app:#{app_root}"
0
+ create_spawner = proc do
0
+ ApplicationSpawner.new(app_root, lower_privilege, lowest_user, environment)
0
+ end
0
+ else
0
+ key = "version:#{framework_version}"
0
+ create_spawner = proc do
0
+ FrameworkSpawner.new(:version => framework_version)
0
+ end
0
       end
0
- elsif framework_version.nil?
0
+ else
0
       app_root = normalize_path(app_root)
0
       key = "app:#{app_root}"
0
       create_spawner = proc do
0
         ApplicationSpawner.new(app_root, lower_privilege, lowest_user, environment)
0
       end
0
- else
0
- key = "version:#{framework_version}"
0
- create_spawner = proc do
0
- FrameworkSpawner.new(:version => framework_version)
0
- end
0
     end
0
     
0
     spawner = nil
0
@@ -111,8 +125,10 @@ class SpawnManager < AbstractServer
0
         if spawner.is_a?(FrameworkSpawner)
0
           return spawner.spawn_application(app_root, lower_privilege,
0
             lowest_user, environment)
0
- else
0
+ elsif spawn_method == "smart"
0
           return spawner.spawn_application
0
+ else
0
+ return spawner.spawn_application!
0
         end
0
       rescue AbstractServer::ServerError
0
         spawner.stop
0
@@ -176,11 +192,12 @@ class SpawnManager < AbstractServer
0
   end
0
 
0
 private
0
- def handle_spawn_application(app_root, lower_privilege, lowest_user, environment)
0
+ def handle_spawn_application(app_root, lower_privilege, lowest_user, environment, spawn_method)
0
     lower_privilege = lower_privilege == "true"
0
     app = nil
0
     begin
0
- app = spawn_application(app_root, lower_privilege, lowest_user, environment)
0
+ app = spawn_application(app_root, lower_privilege, lowest_user,
0
+ environment, spawn_method)
0
     rescue ArgumentError => e
0
       send_error_page(client, 'invalid_app_root', :error => e, :app_root => app_root)
0
     rescue AbstractServer::ServerError => e
...
29
30
31
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
34
35
 
 
36
37
38
...
62
63
64
65
 
 
66
67
68
...
101
102
103
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
106
107
108
109
110
111
 
 
112
113
114
...
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
...
77
78
79
 
80
81
82
83
84
...
117
118
119
 
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
 
142
143
144
145
146
0
@@ -29,10 +29,25 @@ describe SpawnManager do
0
     @stub.destroy
0
   end
0
   
0
- it_should_behave_like "AbstractServer"
0
+ describe "smart spawning" do
0
+ before :each do
0
+ @spawn_method = "smart"
0
+ end
0
+
0
+ it_should_behave_like "AbstractServer"
0
+ end
0
+
0
+ describe "conservative spawning" do
0
+ before :each do
0
+ @spawn_method = "conservative"
0
+ end
0
+
0
+ it_should_behave_like "AbstractServer"
0
+ end
0
   
0
   def spawn_arbitrary_application
0
- @manager.spawn_application(@stub.app_root)
0
+ @manager.spawn_application(@stub.app_root, true, "nobody",
0
+ "production", @spawn_method)
0
   end
0
 end
0
 
0
@@ -62,7 +77,8 @@ describe SpawnManager do
0
         a.close
0
         sleep(1) # Give @manager the chance to start.
0
         channel = MessageChannel.new(b)
0
- channel.write("spawn_application", @stub.app_root, "true", "nobody", "production")
0
+ channel.write("spawn_application", @stub.app_root, "true",
0
+ "nobody", "production", "smart")
0
         channel.read
0
         pid, listen_socket = channel.read
0
         channel.recv_io.close
0
@@ -101,14 +117,30 @@ end
0
 describe SpawnManager do
0
   include TestHelper
0
   
0
- it_should_behave_like "a minimal spawner"
0
+ before :each do
0
+ @spawn_method = "smart"
0
+ end
0
+
0
+ describe "smart spawning" do
0
+ it_should_behave_like "a minimal spawner"
0
+ end
0
+
0
+ describe "conservative spawning" do
0
+ before :each do
0
+ @spawn_method = "conservative"
0
+ end
0
+
0
+ it_should_behave_like "a minimal spawner"
0
+ end
0
+
0
   it_should_behave_like "handling errors in application initialization"
0
   it_should_behave_like "handling errors in framework initialization"
0
   
0
   def spawn_stub_application(stub)
0
     spawner = SpawnManager.new
0
     begin
0
- return spawner.spawn_application(stub.app_root)
0
+ return spawner.spawn_application(stub.app_root, true,
0
+ "nobody", "production", @spawn_method)
0
     ensure
0
       spawner.cleanup
0
     end
...
4
5
6
7
 
8
9
10
...
4
5
6
 
7
8
9
10
0
@@ -4,7 +4,7 @@ require 'passenger/spawn_manager'
0
 
0
 include Passenger
0
 class SpawnManager
0
- def handle_spawn_application(app_root, lower_privilege, lowest_user, environment)
0
+ def handle_spawn_application(app_root, lower_privilege, lowest_user, environment, spawn_method)
0
     client.write('ok')
0
     client.write(1234, "/tmp/nonexistant.socket", false)
0
     client.send_io(STDERR)

Comments

    No one has commented yet.