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 !
Hongli Lai (Phusion) (author)
Sun Mar 02 06:16:42 -0800 2008
commit  2c9448a9dbb9ca2d3a67c77a4b75b00ee8359dee
tree    c4fc9f31b6aa5b949d43c25d2698a42fb6852677
parent  46064f22e9f53c5123404a8c5a0472def8f59dfe
passenger / lib / passenger / application_spawner.rb
100644 183 lines (167 sloc) 5.538 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
116
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
require 'socket'
require 'etc'
require 'passenger/abstract_server'
require 'passenger/application'
require 'passenger/utils'
require 'passenger/request_handler'
module Passenger
 
# Raised when an ApplicationSpawner, FrameworkSpawner or SpawnManager is
# unable to spawn a new application.
class SpawnError < StandardError
end
 
# TODO: check whether preloading the application succeeded. spawn_application()
# should throw an exception with the error message if the app couldn't initialize.
 
# This class is capable of spawns instances of a single Ruby on Rails application.
# It does so by preloading as much of the application's code as possible, then creating
# instances of the application using what is already preloaded. This makes it spawning
# application instances very fast, except for the first spawn.
#
# Use multiple instances of ApplicationSpawner if you need to spawn multiple different
# Ruby on Rails applications.
#
# *Note*: ApplicationSpawner may only be started asynchronously with AbstractServer#start.
# Starting it synchronously with AbstractServer#start_synchronously has not been tested.
class ApplicationSpawner < AbstractServer
  include Utils
  
  # The user ID of the root user.
  ROOT_UID = 0
  # The group ID of the root user.
  ROOT_GID = 0
  
  # An attribute, used internally. This should not be used outside Passenger.
  attr_accessor :time
 
  # +app_root+ is the root directory of this application, i.e. the directory
  # that contains 'app/', 'public/', etc. If given an invalid directory,
  # or a directory that doesn't appear to be a Rails application root directory,
  # then an ArgumentError will be raised.
  #
  # If +lower_privilege+ is true, then ApplicationSpawner will attempt to
  # switch to the user who owns the application's +config/environment.rb+,
  # and to the default group of that user.
  #
  # If that user doesn't exist on the system, or if that user is root,
  # then ApplicationSpawner will attempt to switch to the username given by
  # +lowest_user+ (and to the default group of that user).
  # If +lowest_user+ doesn't exist either, or if switching user failed
  # (because the current process does not have the privilege to do so),
  # then ApplicationSpawner will continue without reporting an error.
  def initialize(app_root, lower_privilege = true, lowest_user = "nobody")
    super()
    @app_root = normalize_path(app_root)
    @lower_privilege = lower_privilege
    @lowest_user = lowest_user
    self.time = Time.now
    assert_valid_app_root(@app_root)
    define_message_handler(:spawn_application, :handle_spawn_application)
  end
  
  # Spawn an instance of the RoR application. When successful, an Application object
  # will be returned, which represents the spawned RoR application.
  #
  # If the ApplicationSpawner server hasn't already been started, a ServerNotStarted
  # will be raised.
  # If the RoR application failed to start, then a SpawnError will be raised. The
  # application's exception message will be printed to standard error.
  def spawn_application
    server.write("spawn_application")
    pid, socket_name, using_abstract_namespace = server.read
    owner_pipe = server.recv_io
    return Application.new(@app_root, pid, socket_name,
      using_abstract_namespace == "true", owner_pipe)
  rescue SystemCallError, IOError, SocketError
    raise SpawnError, "Unable to spawn the application: application died unexpectedly during initialization."
  end
 
protected
  # Overrided method.
  def before_fork # :nodoc:
    if GC.cow_friendly?
      # Garbage collect to so that the child process doesn't have to
      # do that (to prevent making pages dirty).
      GC.start
    end
  end
  
  # Overrided method.
  def initialize_server # :nodoc:
    $0 = "Passenger ApplicationSpawner: #{@app_root}"
    Dir.chdir(@app_root)
    lower_privilege! if @lower_privilege
    preload_application
  end
  
private
  def lower_privilege!
    stat = File.stat("config/environment.rb")
    if !switch_to_user(stat.uid)
      switch_to_user(@lowest_user)
    end
  end
 
  def switch_to_user(user)
    begin
      if user.is_a?(String)
        pw = Etc.getpwnam(user)
        uid = pw.uid
        gid = pw.gid
      else
        uid = user
        gid = Etc.getpwuid(uid).gid
      end
    rescue
      return false
    end
    if uid == ROOT_UID
      return false
    else
      Process::Sys.setgid(gid)
      Process::Sys.setuid(uid)
      return true
    end
  end
 
  def preload_application
    require 'config/environment'
    require_dependency 'application'
    Dir.glob('app/{models,controllers,helpers}/*.rb').each do |file|
      require normalize_path(file)
    end
  end
 
  def handle_spawn_application
    # Double fork to prevent zombie processes.
    pid = fork do
      begin
        pid = fork do
          begin
            start_request_handler
          rescue SignalException => signal
            if e.message != RequestHandler::HARD_TERMINATION_SIGNAL &&
             e.message != RequestHandler::SOFT_TERMINATION_SIGNAL
              print_exception('application', e)
            end
          rescue Exception => e
            print_exception('application', e)
          ensure
            exit!
          end
        end
      rescue Exception => e
        print_exception(self.class.to_s, e)
      ensure
        exit!
      end
    end
    Process.waitpid(pid)
  end
  
  def start_request_handler
    $0 = "Rails: #{@app_root}"
    reader, writer = IO.pipe
    begin
      handler = RequestHandler.new(reader)
      client.write(Process.pid, handler.socket_name,
        handler.using_abstract_namespace?)
      client.send_io(writer)
      writer.close
      client.close
      handler.main_loop
    ensure
      client.close rescue nil
      writer.close rescue nil
      handler.cleanup rescue nil
    end
  end
end
 
end # module Passenger