Skip to content

Instantly share code, notes, and snippets.

@rke
Forked from TonyStrauss/unicorn_reloader
Created August 16, 2013 22:47
Show Gist options
  • Save rke/6254166 to your computer and use it in GitHub Desktop.
Save rke/6254166 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
#
# unicorn_reloader
#
# spawns a unicorn to serve a rack app in the given directory. Watches
# the app files for changes, restarting the unicorn worker when they're
# detected.
#
# Tony Strauss <tony at animoto.com>
# Forked in order to:
# * Have bring down Unicorn as part of its exit sequence
# * Eliminate the need to rely on unicorn's pid file
# * Rename (unicorn_reloader)
#
# This script relies on:
# directory_watcher
# rev
#
# This script also echoes the unicorn output to stdout
#
# This originally was based on Jonathan Stott's watcher.rb (Unicorn Sparkles):
# Jonathan D. Stott <jonathan.stott@gmail.com>
# http://namelessjon.posterous.com/magical-reloading-sparkles
#
# Copyright (c) 2010 Jonathan Stott.
# Copyright (c) 2011 Animoto Productions (http://www.animoto.com)
# Released under the terms of the MIT License
require 'directory_watcher'
def usage
puts "The unicorn_reloader launches unicorn and monitors source files for"
puts "changes. When it detects a change, it restarts the Unicorn workers,"
puts "reloading the web application. This allows you to"
puts "immediately see the results of your changes without any"
puts "manual intervention. While doing so, it also tails the specified"
puts "log files."
puts ""
puts "unicorn_reloader unicorn_config_file rackup_file log_file1 log_file2 ..."
end
if ARGV.empty?
usage
exit
elsif ARGV.length < 2
$stderr.print "Error: invalid arguments!\n\n"
usage
exit false
end
log_files = []
unicorn_config_file = ARGV[0]
rackup_file = ARGV[1]
if ARGV.length > 2
log_files = ARGV[2..ARGV.length]
end
# The application files that will be watched in order to trigger reloads.
GLOB = ['**/*.rb'] # glob of the application's files
# remove the old logs
log_files.each do |log_file|
begin
File.delete log_file
rescue Errno::ENOENT
end
end
# start unicorn via fork/exec
unicorn_pid = Process.fork do
Process.exec "unicorn --config-file #{unicorn_config_file} #{rackup_file}"
end
logs = []
#
# Wait for unicorn to start and create any
# specified log files.
#
log_files.each do |log_file|
begin
logs << File.open(log_file)
rescue Errno::ENOENT
retry
end
end
# watch our app for changes
dw = DirectoryWatcher.new '.',
:glob => GLOB,
:interval => 2,
:scanner => :rev,
:pre_load => true
# SIGHUP makes unicorn respawn workers
dw.add_observer do |*args|
puts "Detected source code change. Restarting Unicorn workers!"
Process.kill :HUP, unicorn_pid
end
trap "INT" do |sig|
# Kill unicorn, which will trigger the unicorn_relaoder to exit below
Process.kill :QUIT, unicorn_pid
end
dw.start
#
# Wait for any one of:
# * Unicorn to exit (caught by waitpid2)
# * unicorn_reloader to be interrupted (resulting in our SIGINT handler being run, which will kill unicorn)
#
unicorn_exit_status = nil
while unicorn_exit_status == nil do
logs.each do |log|
print log.read
end
#
# The sleep is hacky; this could be improved by using the self-pipe trick
#
sleep 0.1
# Non-blocking waitpid
unused, unicorn_exit_status = Process.waitpid2 unicorn_pid, Process::WNOHANG
end
dw.stop
if not unicorn_exit_status.exited? or not unicorn_exit_status.success?
$stderr.puts "\nERROR: Unicorn exited with bad status: #{unicorn_exit_status}"
exit false
else
exit
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment