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 error handling in ApplicationSpawner and FrameworkSpawner. Add 
more tests for error conditions.
Hongli Lai (Phusion) (author)
Mon Mar 03 06:24:54 -0800 2008
commit  d21001ab85169c4bb63c5f26922a5b84b12bcc6c
tree    6b55b6921b8b50e08e2f80216e292404d2533e62
parent  dfa7640170bf3f6153e1d6b98963b583ef121c91
...
18
19
20
21
 
22
23
24
...
81
82
83
84
85
86
87
 
 
 
 
88
89
90
91
92
93
94
95
 
 
96
97
98
...
132
133
134
135
 
136
137
138
...
18
19
20
 
21
22
23
24
...
81
82
83
 
 
 
 
84
85
86
87
88
89
90
91
92
93
 
 
94
95
96
97
98
...
132
133
134
 
135
136
137
138
0
@@ -18,7 +18,7 @@ class InitializationError < StandardError
0
   # The exception that the application instance raised during startup.
0
   # This may be nil, which means that the application instance exited
0
   # with exit() instead of having raised an exception.
0
- attr_reader :child_exception
0
+ attr_accessor :child_exception
0
 
0
   def initialize(message, child_exception = nil)
0
     super(message)
0
@@ -81,18 +81,18 @@ class ApplicationSpawner < AbstractServer
0
   # Spawn an instance of the RoR application. When successful, an Application object
0
   # will be returned, which represents the spawned RoR application.
0
   #
0
- # If the ApplicationSpawner server hasn't already been started, a ServerNotStarted
0
- # will be raised.
0
- # If the RoR application failed to start, then a SpawnError will be raised. The
0
- # application's exception message will be printed to standard error.
0
+ # Raises:
0
+ # - AbstractServer::ServerNotStarted: The ApplicationSpawner server hasn't already been started.
0
+ # - SpawnError: Something went wrong while spawning the application instance.
0
+ # The application instance's exception message will be printed to standard error.
0
   def spawn_application
0
     server.write("spawn_application")
0
     pid, socket_name, using_abstract_namespace = server.read
0
     owner_pipe = server.recv_io
0
     return Application.new(@app_root, pid, socket_name,
0
       using_abstract_namespace == "true", owner_pipe)
0
- rescue SystemCallError, IOError, SocketError
0
- raise SpawnError, "Unable to spawn the application: application died unexpectedly during initialization."
0
+ rescue SystemCallError, IOError, SocketError => e
0
+ raise SpawnError, e.message
0
   end
0
   
0
   # Overrided from AbstractServer#start.
0
@@ -132,7 +132,7 @@ protected
0
       Dir.chdir(@app_root)
0
       lower_privilege! if @lower_privilege
0
       preload_application
0
- rescue => e
0
+ rescue StandardError, ScriptError, NoMemoryError => e
0
       client.write('exception')
0
       client.write_scalar(marshal_exception(e))
0
       return
...
34
35
36
37
 
38
39
 
40
41
 
42
43
44
45
 
46
47
48
...
60
61
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
64
65
66
67
68
69
 
 
70
71
72
...
78
79
80
81
82
83
84
85
86
 
 
 
 
 
 
87
88
89
90
91
92
93
94
95
 
 
 
 
 
 
 
 
 
 
 
96
97
98
99
 
 
100
101
102
...
104
105
106
107
 
108
109
110
...
120
121
122
123
 
124
125
126
...
136
137
138
139
140
141
142
...
147
148
149
 
 
 
 
 
 
 
 
150
151
152
...
194
195
196
197
198
 
 
 
 
 
 
 
 
199
200
201
202
 
 
 
 
 
 
 
 
203
204
205
...
34
35
36
 
37
38
 
39
40
 
41
42
43
44
 
45
46
47
48
...
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
 
 
89
90
91
92
93
...
99
100
101
 
 
 
 
 
 
102
103
104
105
106
107
108
109
110
111
112
 
 
 
 
113
114
115
116
117
118
119
120
121
122
123
124
 
 
 
125
126
127
128
129
...
131
132
133
 
134
135
136
137
...
147
148
149
 
150
151
152
153
...
163
164
165
 
166
167
168
...
173
174
175
176
177
178
179
180
181
182
183
184
185
186
...
228
229
230
 
 
231
232
233
234
235
236
237
238
239
240
241
 
242
243
244
245
246
247
248
249
250
251
252
0
@@ -34,15 +34,15 @@ class FrameworkSpawner < AbstractServer
0
   #
0
   # Valid options:
0
   # - <tt>:version</tt>: The Ruby on Rails version to use. It is not checked whether
0
- # this version is actually installed.
0
+ # this version is actually installed.
0
   # - <tt>:vendor</tt>: The directory to the vendor Rails framework to use. This is
0
- # usually something like "/webapps/foo/vendor/rails".
0
+ # usually something like "/webapps/foo/vendor/rails".
0
   #
0
- # It is not allowed to specify both <tt>version</tt> and <tt>vendor</tt>.
0
+ # It is not allowed to specify both +version+ and +vendor+.
0
   #
0
   # Note that the specified Rails framework will be loaded during the entire life time
0
   # of the FrameworkSpawner server. If you wish to reload the Rails framework's code,
0
- # then restart the server by calling stop() and start().
0
+ # then restart the server by calling AbstractServer#stop and AbstractServer#start.
0
   def initialize(options = {})
0
     if !options.respond_to?(:'[]')
0
       raise ArgumentError, "The 'options' argument not seem to be an options hash"
0
@@ -60,13 +60,34 @@ class FrameworkSpawner < AbstractServer
0
     define_message_handler(:reload, :handle_reload)
0
   end
0
   
0
+ # Overrided from AbstractServer#start.
0
+ #
0
+ # This method raises InitializationError if the given Ruby on Rails framework
0
+ # could not be loaded.
0
+ def start
0
+ super
0
+ status = server.read[0]
0
+ if status == 'exception'
0
+ child_exception = unmarshal_exception(server.read_scalar)
0
+ stop
0
+ if @version
0
+ message = "Could not load Ruby on Rails framework version #{@version}: " <<
0
+ "#{child_exception.class} (#{child_exception.message})"
0
+ else
0
+ message = "Could not load Ruby on Rails framework at '#{@vendor}': " <<
0
+ "#{child_exception.class} (#{child_exception.message})"
0
+ end
0
+ raise InitializationError.new(message, child_exception)
0
+ end
0
+ end
0
+
0
   # Spawn a RoR application using the Ruby on Rails framework
0
   # version associated with this FrameworkSpawner.
0
   # When successful, an Application object will be returned, which represents
0
   # the spawned RoR application.
0
   #
0
- # See ApplicationSpawner.new() for an explanation for the _lower_privilege_
0
- # and _lowest_user_ parameters.
0
+ # See ApplicationSpawner.new for an explanation for the +lower_privilege+
0
+ # and +lowest_user+ parameters.
0
   #
0
   # FrameworkSpawner will internally use ApplicationSpawner, and cache ApplicationSpawner
0
   # objects for a while. As a result, spawning an instance of a RoR application for the
0
@@ -78,25 +99,31 @@ class FrameworkSpawner < AbstractServer
0
   # - Restart this FrameworkSpawner by calling stop(), then start().
0
   # - Reload the application by calling reload().
0
   #
0
- # If the FrameworkSpawner server hasn't already been started, a ServerNotStarted
0
- # will be raised.
0
- # If the RoR application failed to start (which may be a problem in the application,
0
- # or a problem in the Ruby on Rails framework), then a SpawnError
0
- # will be raised. The application's exception message will be printed to standard
0
- # error.
0
+ # Raises:
0
+ # - AbstractServer::ServerNotStarted: The FrameworkSpawner server hasn't already been started.
0
+ # - ArgumentError: +app_root+ doesn't appear to be a valid Ruby on Rails application root.
0
+ # - InitializationError: The application raised an exception or called exit() during startup.
0
+ # - SpawnError: Something went wrong while spawning the application instance.
0
+ # The application instance's exception message will be printed to standard error.
0
   def spawn_application(app_root, lower_privilege = true, lowest_user = "nobody")
0
     app_root = normalize_path(app_root)
0
     assert_valid_app_root(app_root)
0
     begin
0
       server.write("spawn_application", app_root, lower_privilege, lowest_user)
0
- pid, listen_socket_name, using_abstract_namespace = server.read
0
- owner_pipe = server.recv_io
0
- return Application.new(app_root, pid, listen_socket_name,
0
- using_abstract_namespace == "true", owner_pipe)
0
+ result = server.read
0
+ if result.nil?
0
+ raise IOError, "Connection closed"
0
+ elsif result[0] == 'exception'
0
+ raise unmarshal_exception(server.read_scalar)
0
+ else
0
+ pid, listen_socket_name, using_abstract_namespace = server.read
0
+ owner_pipe = server.recv_io
0
+ return Application.new(app_root, pid, listen_socket_name,
0
+ using_abstract_namespace == "true", owner_pipe)
0
+ end
0
     rescue SystemCallError, IOError, SocketError
0
- raise SpawnError, "Unable to spawn the application: " <<
0
- "either the Ruby on Rails framework failed to load, " <<
0
- "or the application died unexpectedly during initialization."
0
+ # TODO: consider restarting the corresponding framework spawner
0
+ raise SpawnError, "The framework spawner server exited unexpectedly"
0
     end
0
   end
0
   
0
@@ -104,7 +131,7 @@ class FrameworkSpawner < AbstractServer
0
   # If nil is specified as application root, then all cached application
0
   # instances will be removed, no matter the application root.
0
   #
0
- # _Long description:_
0
+ # <b>Long description:</b>
0
   # Application code might be cached in memory by a FrameworkSpawner. But
0
   # once it a while, it will be necessary to reload the code for an
0
   # application, such as after deploying a new version of the application.
0
@@ -120,7 +147,7 @@ class FrameworkSpawner < AbstractServer
0
       server.write("reload", normalize_path(app_root))
0
     end
0
   rescue SystemCallError, IOError, SocketError
0
- raise IOError, "Cannot send reload command to the framework spawner server."
0
+ raise IOError, "Cannot send reload command to the framework spawner server"
0
   end
0
 
0
 protected
0
@@ -136,7 +163,6 @@ protected
0
   # Overrided method.
0
   def initialize_server # :nodoc:
0
     $0 = "Passenger FrameworkSpawner: #{@version || @vendor}"
0
- preload_rails
0
     @spawners = {}
0
     @spawners_lock = Mutex.new
0
     @spawners_cond = ConditionVariable.new
0
@@ -147,6 +173,14 @@ protected
0
         print_exception(self.class.to_s, e)
0
       end
0
     end
0
+ begin
0
+ preload_rails
0
+ rescue StandardError, ScriptError, NoMemoryError => e
0
+ client.write('exception')
0
+ client.write_scalar(marshal_exception(e))
0
+ return
0
+ end
0
+ client.write('success')
0
   end
0
   
0
   # Overrided method.
0
@@ -194,12 +228,25 @@ private
0
     @spawners_lock.synchronize do
0
       spawner = @spawners[app_root]
0
       if spawner.nil?
0
- spawner = ApplicationSpawner.new(app_root, lower_privilege, lowest_user)
0
- spawner.start
0
+ begin
0
+ spawner = ApplicationSpawner.new(app_root, lower_privilege, lowest_user)
0
+ spawner.start
0
+ rescue ArgumentError, InitializationError => e
0
+ client.write('exception')
0
+ client.write_scalar(marshal_exception(e))
0
+ return
0
+ end
0
         @spawners[app_root] = spawner
0
       end
0
       spawner.time = Time.now
0
- app = spawner.spawn_application
0
+ begin
0
+ app = spawner.spawn_application
0
+ rescue SpawnError => e
0
+ client.write('exception')
0
+ client.write_scalar(marshal_exception(e))
0
+ return
0
+ end
0
+ client.write('success')
0
       client.write(app.pid, app.listen_socket_name, app.using_abstract_namespace?)
0
       client.send_io(app.owner_pipe)
0
       app.close
...
66
67
68
69
70
71
72
73
 
 
 
 
 
 
 
 
 
 
 
 
 
74
75
76
77
78
79
80
81
82
83
 
 
 
 
 
 
 
 
 
 
 
 
 
84
85
86
...
66
67
68
 
69
70
71
72
73
74
75
76
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
102
103
104
105
106
0
@@ -66,21 +66,41 @@ protected
0
   
0
   def marshal_exception(exception)
0
     data = {
0
- :exception => Marshal.dump(exception),
0
       :message => exception.message,
0
       :class => exception.class.to_s,
0
       :backtrace => exception.backtrace
0
     }
0
+ if exception.is_a?(InitializationError)
0
+ if exception.child_exception
0
+ data[:child_exception] = marshal_exception(exception.child_exception)
0
+ end
0
+ else
0
+ begin
0
+ data[:exception] = Marshal.dump(exception)
0
+ rescue ArgumentError, TypeError
0
+ e = UnknownError.new(exception.message, exception.class.to_s,
0
+ exception.backtrace)
0
+ data[:exception] = Marshal.dump(e)
0
+ end
0
+ end
0
     return Marshal.dump(data)
0
   end
0
   
0
   def unmarshal_exception(data)
0
     hash = Marshal.load(data)
0
- begin
0
- return Marshal.load(hash[:exception])
0
- rescue ArgumentError
0
- exception = UnknownError.new(hash[:message], hash[:class], hash[:backtrace])
0
- return exception
0
+ if hash[:class] == InitializationError.to_s
0
+ if hash[:child_exception]
0
+ child_exception = unmarshal_exception(hash[:child_exception])
0
+ else
0
+ child_exception = nil
0
+ end
0
+ return InitializationError.new(hash[:message], child_exception)
0
+ else
0
+ begin
0
+ return Marshal.load(hash[:exception])
0
+ rescue ArgumentError, TypeError
0
+ return UnknownError.new(hash[:message], hash[:class], hash[:backtrace])
0
+ end
0
     end
0
   end
0
   
...
6
7
8
9
10
11
12
...
34
35
36
37
38
39
40
 
41
42
43
...
6
7
8
 
9
10
11
...
33
34
35
 
36
37
 
38
39
40
41
0
@@ -6,7 +6,6 @@ require 'spawner_privilege_lowering_spec'
0
 require 'spawner_error_handling_spec'
0
 include Passenger
0
 
0
-if false
0
 describe ApplicationSpawner do
0
   before :all do
0
     ENV['RAILS_ENV'] = 'production'
0
@@ -34,10 +33,9 @@ describe ApplicationSpawner do
0
     @spawner.spawn_application
0
   end
0
 end
0
-end
0
 
0
 describe ApplicationSpawner do
0
- it_should_behave_like "a spawner that correctly handles errors"
0
+ it_should_behave_like "handling errors in application initialization"
0
   
0
   def spawn_application(app_root)
0
     @spawner = ApplicationSpawner.new(app_root)
...
3
4
5
 
6
7
8
...
29
30
31
32
 
33
34
35
36
37
38
39
40
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
43
44
...
3
4
5
6
7
8
9
...
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
0
@@ -3,6 +3,7 @@ require 'passenger/framework_spawner'
0
 require 'minimal_spawner_spec'
0
 require 'spawn_server_spec'
0
 require 'spawner_privilege_lowering_spec'
0
+require 'spawner_error_handling_spec'
0
 include Passenger
0
 
0
 describe FrameworkSpawner do
0
@@ -29,16 +30,44 @@ describe FrameworkSpawner do
0
   it_should_behave_like "a spawn server"
0
   
0
   it "should support vendor Rails" do
0
- # TODO
0
+ # Already being tested by all the other tests.
0
   end
0
   
0
- # TODO: test reloading
0
-
0
   def spawn_application
0
     @spawner.spawn_application(@test_app)
0
   end
0
 end
0
 
0
+describe FrameworkSpawner do
0
+ it_should_behave_like "handling errors in application initialization"
0
+ it_should_behave_like "handling errors in framework initialization"
0
+
0
+ def spawn_application(app_root)
0
+ version = Application.detect_framework_version(app_root)
0
+ if version.nil?
0
+ options = { :vendor => "#{@app_root}/vendor/rails" }
0
+ else
0
+ options = { :version => version }
0
+ end
0
+ spawner = FrameworkSpawner.new(options)
0
+ spawner.start
0
+ begin
0
+ return spawner.spawn_application(app_root)
0
+ ensure
0
+ spawner.stop
0
+ end
0
+ end
0
+
0
+ def load_nonexistant_framework
0
+ spawner = FrameworkSpawner.new(:version => "1.9.827")
0
+ begin
0
+ spawner.start
0
+ ensure
0
+ spawner.stop rescue nil
0
+ end
0
+ end
0
+end
0
+
0
 if Process.euid == ApplicationSpawner::ROOT_UID
0
   describe "FrameworkSpawner privilege lowering support" do
0
     before :all do
...
191
192
193
 
 
 
 
194
195
196
...
191
192
193
194
195
196
197
198
199
200
0
@@ -191,6 +191,10 @@ describe "mod_passenger running in Apache 2" do
0
     it "should be possible to specify RailsAutoDetect in .htaccess"
0
   end
0
   
0
+ describe "error handling" do
0
+ it "should not crash if the RoR application crashes"
0
+ end
0
+
0
   ##### Helper methods #####
0
   
0
   def get(uri)
...
7
8
9
10
 
11
12
13
14
15
16
 
17
18
19
...
7
8
9
 
10
11
12
13
14
15
 
16
17
18
19
0
@@ -7,13 +7,13 @@ shared_examples_for "a spawn server" do
0
   it "should raise a SpawnError if something went wrong" do
0
     Process.kill('SIGABRT', @spawner.server_pid)
0
     spawning = lambda { spawn_application }
0
- spawning.should raise_error(ApplicationSpawner::SpawnError)
0
+ spawning.should raise_error(SpawnError)
0
   end
0
   
0
   it "should work correctly after a restart, if something went wrong" do
0
     Process.kill('SIGABRT', @spawner.server_pid)
0
     spawning = lambda { spawn_application }
0
- spawning.should raise_error(ApplicationSpawner::SpawnError)
0
+ spawning.should raise_error(SpawnError)
0
     
0
     @spawner.stop
0
     @spawner.start
...
1
 
2
3
4
...
26
27
28
 
 
 
 
 
 
 
...
 
1
2
3
4
...
26
27
28
29
30
31
32
33
34
35
0
@@ -1,4 +1,4 @@
0
-shared_examples_for "a spawner that correctly handles errors" do
0
+shared_examples_for "handling errors in application initialization" do
0
   it "should raise an InitializationError if the spawned app raises a standard exception during startup" do
0
     begin
0
       spawn_application('stub/broken-railsapp')
0
@@ -26,3 +26,10 @@ shared_examples_for "a spawner that correctly handles errors" do
0
     end
0
   end
0
 end
0
+
0
+shared_examples_for "handling errors in framework initialization" do
0
+ include Utils
0
+ it "should raise InitializationError if the framework could not be loaded" do
0
+ lambda { load_nonexistant_framework }.should raise_error(InitializationError)
0
+ end
0
+end

Comments

    No one has commented yet.