fix initialization issues with unicorn

amend unicorn script to demonize sidekiq
create a sidekiq demon that unicorn consumes
correct bug in exec_sql with empty params
This commit is contained in:
Sam 2013-10-10 13:33:52 +11:00
parent 15de4ac890
commit c4bab8915c
4 changed files with 190 additions and 37 deletions

View File

@ -11,8 +11,7 @@ Thread.new do
old_time = File.ctime(file).to_i if File.exists? file old_time = File.ctime(file).to_i if File.exists? file
wait_seconds = 4 wait_seconds = 4
return if $PROGRAM_NAME !~ /thin/ if $PROGRAM_NAME =~ /thin/
while true while true
time = File.ctime(file).to_i if File.exists? file time = File.ctime(file).to_i if File.exists? file
@ -22,9 +21,10 @@ Thread.new do
sleep wait_seconds sleep wait_seconds
Rails.logger.info "restarting #{$$}" Rails.logger.info "restarting #{$$}"
Process.kill("HUP", $$) Process.kill("HUP", $$)
return break
end end
sleep 1 sleep 1
end end
end
end end

View File

@ -3,11 +3,12 @@
discourse_path = File.expand_path(File.expand_path(File.dirname(__FILE__)) + "/../") discourse_path = File.expand_path(File.expand_path(File.dirname(__FILE__)) + "/../")
# tune down if not enough ram # tune down if not enough ram
worker_processes 2 worker_processes 3
working_directory discourse_path working_directory discourse_path
listen 8080, :tcp_nopush => true # listen "#{discourse_path}/tmp/sockets/unicorn.sock"
listen 3000
# nuke workers after 30 seconds instead of 60 seconds (the default) # nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30 timeout 30
@ -34,42 +35,26 @@ check_client_connection false
initialized = false initialized = false
before_fork do |server, worker| before_fork do |server, worker|
unless initialized unless initialized
# load up the yaml for the localization bits, in master process # load up the yaml for the localization bits, in master process
I18n.t(:posts) I18n.t(:posts)
# get rid of rubbish so we don't share it # get rid of rubbish so we don't share it
GC.start GC.start
require 'demon/sidekiq'
Demon::Sidekiq.start(1)
end end
ActiveRecord::Base.connection.disconnect! ActiveRecord::Base.connection.disconnect!
$redis.client.disconnect $redis.client.disconnect
#TODO
# at this point we want to fork out sidekiq, it will let us reuse the shared memory
# The following is only recommended for memory/DB-constrained
# installations. It is not needed if your system can house
# twice as many worker_processes as you have configured.
#
# # This allows a new master process to incrementally
# # phase out the old master process with SIGTTOU to avoid a
# # thundering herd (especially in the "preload_app false" case)
# # when doing a transparent upgrade. The last worker spawned
# # will then kill off the old master process with a SIGQUIT.
# old_pid = "#{server.config[:pid]}.oldbin"
# if old_pid != server.pid
# begin
# sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
# Process.kill(sig, File.read(old_pid).to_i)
# rescue Errno::ENOENT, Errno::ESRCH
# end
# end
#
# Throttle the master from forking too quickly by sleeping. Due # Throttle the master from forking too quickly by sleeping. Due
# to the implementation of standard Unix signal handlers, this # to the implementation of standard Unix signal handlers, this
# helps (but does not completely) prevent identical, repeated signals # helps (but does not completely) prevent identical, repeated signals
# from being lost when the receiving process is busy. # from being lost when the receiving process is busy.
# sleep 1 sleep 1
end end
after_fork do |server, worker| after_fork do |server, worker|

164
lib/demon/sidekiq.rb Normal file
View File

@ -0,0 +1,164 @@
module Demon; end
# intelligent fork based demonizer for sidekiq
class Demon::Base
def self.start(count)
@demons ||= {}
count.times do |i|
(@demons["#{prefix}_#{i}"] ||= new(i)).start
end
end
def self.stop
@demons.values.each do |demon|
demon.stop
end
end
def initialize(index)
@index = index
@pid = nil
@parent_pid = Process.pid
@monitor = nil
end
def pid_file
"#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid"
end
def stop
if @monitor
@monitor.kill
@monitor.join
@monitor = nil
end
if @pid
Process.kill("SIGHUP",@pid)
@pid = nil
end
end
def start
if existing = already_running?
# should not happen ... so kill violently
Process.kill("SIGTERM",existing)
end
return if @pid
if @pid = fork
write_pid_file
monitor_child
return
end
monitor_parent
establish_app
after_fork
end
def already_running?
if File.exists? pid_file
pid = File.read(pid_file).to_i
if alive?(pid)
return pid
end
end
nil
end
private
def monitor_child
@monitor ||= Thread.new do
while true
sleep 5
unless alive?(@pid)
STDERR.puts "#{@pid} died, restarting sidekiq"
@pid = nil
start
end
end
end
end
def write_pid_file
FileUtils.mkdir_p(Rails.root + "tmp/pids")
File.open(pid_file,'w') do |f|
f.write(@pid)
end
end
def delete_pid_file
File.delete(pid_file)
end
def monitor_parent
Thread.new do
while true
exit unless alive?(@parent_pid)
sleep 1
end
end
end
def alive?(pid)
begin
Process.getpgid(pid)
true
rescue Errno::ESRCH
false
end
end
def establish_app
ActiveRecord::Base.connection_handler.clear_active_connections!
ActiveRecord::Base.establish_connection
$redis.client.reconnect
Rails.cache.reconnect
MessageBus.after_fork
Signal.trap("HUP") do
begin
delete_pid_file
ensure
exit
end
end
# keep stuff simple for now
$stdout.reopen("/dev/null", "w")
# $stderr.reopen("/dev/null", "w")
end
def after_fork
end
end
class Demon::Sidekiq < Demon::Base
def self.prefix
"sidekiq"
end
private
def after_fork
require 'sidekiq/cli'
begin
cli = Sidekiq::CLI.instance
cli.parse([])
cli.run
rescue => e
STDERR.puts e.message
STDERR.puts e.backtrace.join("\n")
exit 1
end
end
end

View File

@ -60,10 +60,14 @@ class SqlBuilder
sql = to_sql sql = to_sql
if @klass if @klass
@klass.find_by_sql(ActiveRecord::Base.send(:sanitize_sql_array, [sql, @args])) @klass.find_by_sql(ActiveRecord::Base.send(:sanitize_sql_array, [sql, @args]))
else
if @args == {}
ActiveRecord::Base.exec_sql(sql)
else else
ActiveRecord::Base.exec_sql(sql,@args) ActiveRecord::Base.exec_sql(sql,@args)
end end
end end
end
#AS reloads this on tests #AS reloads this on tests
remove_const :FTYPE_MAP if defined? FTYPE_MAP remove_const :FTYPE_MAP if defined? FTYPE_MAP