From c4bab8915cf8903617d080c40d71fc7b624f3cc4 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 10 Oct 2013 13:33:52 +1100 Subject: [PATCH] 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 --- config/initializers/watch_for_restart.rb | 24 ++-- config/unicorn.sample.conf.rb | 33 ++--- lib/demon/sidekiq.rb | 164 +++++++++++++++++++++++ lib/sql_builder.rb | 6 +- 4 files changed, 190 insertions(+), 37 deletions(-) create mode 100644 lib/demon/sidekiq.rb diff --git a/config/initializers/watch_for_restart.rb b/config/initializers/watch_for_restart.rb index 04a1db64d9e..c96cce8377b 100644 --- a/config/initializers/watch_for_restart.rb +++ b/config/initializers/watch_for_restart.rb @@ -11,20 +11,20 @@ Thread.new do old_time = File.ctime(file).to_i if File.exists? file wait_seconds = 4 - return if $PROGRAM_NAME !~ /thin/ + if $PROGRAM_NAME =~ /thin/ + while true + time = File.ctime(file).to_i if File.exists? file - while true - time = File.ctime(file).to_i if File.exists? file + if old_time != time + Rails.logger.info "attempting to reload #{$$} #{$PROGRAM_NAME} in #{wait_seconds} seconds" + $shutdown = true + sleep wait_seconds + Rails.logger.info "restarting #{$$}" + Process.kill("HUP", $$) + break + end - if old_time != time - Rails.logger.info "attempting to reload #{$$} #{$PROGRAM_NAME} in #{wait_seconds} seconds" - $shutdown = true - sleep wait_seconds - Rails.logger.info "restarting #{$$}" - Process.kill("HUP", $$) - return + sleep 1 end - - sleep 1 end end diff --git a/config/unicorn.sample.conf.rb b/config/unicorn.sample.conf.rb index 9887490dbba..5ae942a04ab 100644 --- a/config/unicorn.sample.conf.rb +++ b/config/unicorn.sample.conf.rb @@ -3,11 +3,12 @@ discourse_path = File.expand_path(File.expand_path(File.dirname(__FILE__)) + "/../") # tune down if not enough ram -worker_processes 2 +worker_processes 3 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) timeout 30 @@ -34,42 +35,26 @@ check_client_connection false initialized = false before_fork do |server, worker| + unless initialized # load up the yaml for the localization bits, in master process I18n.t(:posts) # get rid of rubbish so we don't share it GC.start + + require 'demon/sidekiq' + Demon::Sidekiq.start(1) end + ActiveRecord::Base.connection.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 # to the implementation of standard Unix signal handlers, this # helps (but does not completely) prevent identical, repeated signals # from being lost when the receiving process is busy. - # sleep 1 + sleep 1 end after_fork do |server, worker| diff --git a/lib/demon/sidekiq.rb b/lib/demon/sidekiq.rb new file mode 100644 index 00000000000..26ab540ace3 --- /dev/null +++ b/lib/demon/sidekiq.rb @@ -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 diff --git a/lib/sql_builder.rb b/lib/sql_builder.rb index 9858144eaa7..bacef0ad369 100644 --- a/lib/sql_builder.rb +++ b/lib/sql_builder.rb @@ -61,7 +61,11 @@ class SqlBuilder if @klass @klass.find_by_sql(ActiveRecord::Base.send(:sanitize_sql_array, [sql, @args])) else - ActiveRecord::Base.exec_sql(sql,@args) + if @args == {} + ActiveRecord::Base.exec_sql(sql) + else + ActiveRecord::Base.exec_sql(sql,@args) + end end end