Class: Mizuno::Runner

Inherits:
Object
  • Object
show all
Includes:
Daemonizer
Defined in:
lib/mizuno/runner.rb

Overview

Launches Mizuno when called from the command-line, and handles damonization via FFI.

Daemonization code based on Spoon.

Class Method Summary collapse

Methods included from Daemonizer

included?

Class Method Details

.connect_to_server_as_client(server_options, timeout) ⇒ Object



270
271
272
273
274
275
276
277
# File 'lib/mizuno/runner.rb', line 270

def Runner.connect_to_server_as_client(server_options, timeout)
    options = server_options.dup
    options[:host] = '127.0.0.1' if options[:host] == "0.0.0.0"
    Net::HTTP.start(options[:host], options[:port]) do |http|
        http.read_timeout = timeout
        http.get("/")
    end
end

.daemonize(options) ⇒ Object

Relaunch as a daemon.



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
# File 'lib/mizuno/runner.rb', line 120

def Runner.daemonize(options)
    # Ensure that Mizuno isn't running.
    Runner.pid(options) and die("Mizuno is already running.")

    # Build a command line that should launch JRuby with the
    # appropriate options; this depends on the proper jruby
    # being in the $PATH
    config = options.delete(:config)
    args = Mizuno::LAUNCH_ENV.concat(options.map { |k, v| 
        (v.to_s.empty?) ? nil : [ "--#{k}", v.to_s ] }.compact.flatten)
    args.push(config)
    args.unshift('jruby')

    # Launch a detached child process.
    child = ChildProcess.build(*args)
    child.io.inherit!
    child.detach = true
    child.start
    File.open(options[:pidfile], 'w') { |f| f.puts(child.pid) }

    # Wait until the server starts or we time out waiting for it.
    exit if wait_for_server(options, 60)
    child.stop
    die("Failed to start Mizuno.")
end

.die(message, success = false) ⇒ Object

Exit with a message and a status value.

FIXME: Dump these in the logfile if called from Server?



284
285
286
287
# File 'lib/mizuno/runner.rb', line 284

def Runner.die(message, success = false)
    $stderr.puts(message)
    exit(success ? 0 : 1)
end

.kill(options) ⇒ Object

Really stop a running daemon (SIGTERM)



184
185
186
187
188
189
190
191
# File 'lib/mizuno/runner.rb', line 184

def Runner.kill(options)
    pid = Runner.pid(options) or die("Mizuno isn't running.")
    $stderr.puts "Terminating Mizuno with extreme prejudice..."
    Process.kill("TERM", pid)
    die("failed") unless wait_for_server_to_die(options)
    FileUtils.rm(options[:pidfile])
    die("stopped", true)
end

.pid(options) ⇒ Object

Fetches the PID from the :pidfile.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/mizuno/runner.rb', line 205

def Runner.pid(options)
    options[:pidfile] or die("Speficy a --pidfile to daemonize.") 
    return unless File.exists?(options[:pidfile])
    pid = File.read(options[:pidfile]).to_i

    # FIXME: This is a hacky way to get the process list, but I
    # haven't found a good cross-platform solution yet; this
    # should work on MacOS and Linux, possibly Solaris and BSD,
    # and almost definitely not on Windows.
    process = `ps ax`.lines.select { |l| l =~ /^\s*#{pid}\s*/ }
    return(pid) if (process.join =~ /\bmizuno\b/)

    # Stale pidfile; remove.
    $stderr.puts("Removing stale pidfile '#{options[:pidfile]}'")
    FileUtils.rm(options[:pidfile])
    return(nil)
end

.reload(options) ⇒ Object

Reload a running daemon by SIGHUPing it.



160
161
162
163
164
165
166
167
# File 'lib/mizuno/runner.rb', line 160

def Runner.reload(options)
    pid = Runner.pid(options)
    return(Runner.daemonize(options)) \
        if(pid.nil? and options.delete(:restart))
    die("Mizuno is currently not running.") unless pid
    Process.kill("HUP", pid)
    die("Mizuno signaled to reload app.", true)
end

.resolve_path(root, path) ⇒ Object

Transform a relative path to an absolute path.



196
197
198
199
200
# File 'lib/mizuno/runner.rb', line 196

def Runner.resolve_path(root, path)
    return(path) unless path.is_a?(String)
    return(path) if (path =~ /^\//)
    File.expand_path(File.join(root, path))
end

.start(options) ⇒ Object



107
108
109
110
111
112
113
114
115
# File 'lib/mizuno/runner.rb', line 107

def Runner.start(options)
    Dir.chdir(options[:root])
    Logger.configure(options)
    ENV['RACK_ENV'] = options[:env]
    server = Rack::Server.new
    server.options = options.merge(:server => 'mizuno',
        :environment => options[:env])
    server.start
end

.start!Object

Launch Jetty, optionally as a daemon.



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
# File 'lib/mizuno/runner.rb', line 79

def Runner.start!
    # Default rackup is in config.ru
    config = (Choice.rest.first or "config.ru")

    # Create an options hash with only symbols.
    choices = Choice.choices.merge(:config => config)
    options = Hash[choices.map { |k, v| [ k.to_sym, v ] }]

    # Resolve relative paths to the logfile, etc.
    root = options[:root]
    options[:pidfile] = Runner.resolve_path(root, options[:pidfile])
    options[:log] = Runner.resolve_path(root, options[:log])
    options[:public] = Runner.resolve_path(root, options[:public])

    # Require multiple libraries.
    options.delete(:require).each { |r| require r }

    # Handle daemon-related commands.
    Runner.status(options) if options.delete(:status)
    Runner.reload(options) if options.delete(:reload)
    Runner.stop(options) if options.delete(:stop)
    Runner.kill(options) if options.delete(:kill)
    Runner.daemonize(options) if options.delete(:daemonize)

    # Fire up Mizuno as if it was called from Rackup.
    Runner.start(options)
end

.status(options) ⇒ Object

Return the status of a running daemon.



149
150
151
152
153
154
155
# File 'lib/mizuno/runner.rb', line 149

def Runner.status(options)
    die("Mizuno doesn't appear to be running.") \
        unless (pid = Runner.pid(options))
    die("Mizuno is running, but not online.") \
        unless(wait_for_server(options))
    die("Mizuno is running.", true)
end

.stop(options) ⇒ Object

Stop a running daemon (SIGKILL)



172
173
174
175
176
177
178
179
# File 'lib/mizuno/runner.rb', line 172

def Runner.stop(options)
    pid = Runner.pid(options) or die("Mizuno isn't running.")
    print "Stopping Mizuno..."
    Process.kill("KILL", pid)
    die("failed") unless wait_for_server_to_die(options)
    FileUtils.rm(options[:pidfile])
    die("stopped", true)
end

.wait_for_server(options, timeout = 120) ⇒ Object

Wait until timeout seconds for a successful http connection; returns true if we could connect and didn’t get a server error, false otherwise.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/mizuno/runner.rb', line 228

def Runner.wait_for_server(options, timeout = 120)
    force_time_out_at = Time.now + timeout
    sleep_interval_for_next_retry = 0.1

    begin
        response = connect_to_server_as_client(options, timeout)
        return(response.code.to_i < 500)
    rescue Errno::ECONNREFUSED => error
        return(false) if (Time.now > force_time_out_at)
        sleep(sleep_interval_for_next_retry)
        sleep_interval_for_next_retry *= 2
        retry
    rescue => error
        puts "HTTP Error '#{error}'"
        return(false)
    end
end

.wait_for_server_to_die(options, timeout = 120) ⇒ Object

Like wait_for_server, but returns true when the server goes offline. If we hit timeout seconds and the server is still responding, returns false.



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/mizuno/runner.rb', line 251

def Runner.wait_for_server_to_die(options, timeout = 120)
    force_time_out_at = Time.now + timeout
    sleep_interval_for_next_retry = 0.1

    begin
        while (Time.now < force_time_out_at)
            connect_to_server_as_client(options, timeout)
            sleep(sleep_interval_for_next_retry)
            sleep_interval_for_next_retry *= 2
        end
        return(false)
    rescue Errno::ECONNREFUSED => error
        return(true)
    rescue => error
        puts "**** http error: #{error}"
        return(true)
    end
end