Module: Daemonz
- Defined in:
- lib/daemonz/railtie.rb,
lib/daemonz.rb,
lib/daemonz/config.rb,
lib/daemonz/killer.rb,
lib/daemonz/manage.rb,
lib/daemonz/master.rb,
lib/daemonz/logging.rb,
lib/daemonz/process.rb,
lib/daemonz/generators/config/config_generator.rb,
lib/daemonz/generators/daemon/daemon_generator.rb
Overview
:nodoc: namespace
Defined Under Namespace
Modules: Generators, ProcTable Classes: Railtie
Class Attribute Summary collapse
-
.config ⇒ Object
readonly
Returns the value of attribute config.
-
.daemons ⇒ Object
readonly
Returns the value of attribute daemons.
-
.keep_daemons_at_exit ⇒ Object
Set by the rake tasks.
Class Method Summary collapse
-
.claim_master ⇒ Object
attempts to claim the master lock.
-
.configure(config_file, options = {}) ⇒ Object
figure out the plugin’s configuration.
-
.configure_daemons ⇒ Object
process the daemon configuration.
- .configure_logger ⇒ Object
-
.disabled? ⇒ Boolean
compute whether daemonz should be enabled or not.
- .disabled_without_cache! ⇒ Object
- .grab_master_lock ⇒ Object
-
.kill_process_set(kill_script, pid_patterns, process_patterns, options = {}) ⇒ Object
Complex procedure for killing a process or a bunch of process replicas kill_command is the script that’s supposed to kill the process / processes (tried first) pid_patters are globs identifying PID files (a file can match any of the patterns) process_patterns are strings that should show on a command line (a process must match all) options: :verbose - log what gets killed :script_delay - the amount of seconds to sleep after launching the kill script :force_script - the kill script is executed even if there are no PID files.
-
.load_configuration(config_file) ⇒ Object
load and parse the config file.
- .logger ⇒ Object
-
.process_info(pid = nil) ⇒ Object
returns information about a process or all the running processes.
- .release_master_lock ⇒ Object
-
.safe_start(options = {}) ⇒ Object
Complete startup used by rake:start and at Rails plug-in startup.
-
.safe_stop(options = {}) ⇒ Object
Complete shutdown used by rake:start and at Rails application exit.
- .start_daemon!(daemon) ⇒ Object
- .start_daemons! ⇒ Object
- .start_daemons_sync ⇒ Object
- .stop_daemon!(daemon) ⇒ Object
- .stop_daemons! ⇒ Object
-
.with_daemons(logger = 'rails') ⇒ Object
Starts daemons, yields, stops daemons.
Class Attribute Details
.config ⇒ Object (readonly)
Returns the value of attribute config.
6 7 8 |
# File 'lib/daemonz/config.rb', line 6 def config @config end |
.daemons ⇒ Object (readonly)
Returns the value of attribute daemons.
86 87 88 |
# File 'lib/daemonz/config.rb', line 86 def daemons @daemons end |
.keep_daemons_at_exit ⇒ Object
Set by the rake tasks.
9 10 11 |
# File 'lib/daemonz/config.rb', line 9 def keep_daemons_at_exit @keep_daemons_at_exit end |
Class Method Details
.claim_master ⇒ Object
attempts to claim the master lock
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/daemonz/master.rb', line 41 def self.claim_master begin # try to grab that lock master_pid = grab_master_lock if master_pid logger.info "Daemonz in slave mode; PID #{master_pid} has master lock" return false else logger.info "Daemonz grabbed master lock" return true end rescue Exception => e logger.warn "Daemonz mastering failed: #{e.class.name} - #{e}" return false end end |
.configure(config_file, options = {}) ⇒ Object
figure out the plugin’s configuration
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 |
# File 'lib/daemonz/config.rb', line 27 def self.configure(config_file, = {}) load_configuration config_file config[:root_path] ||= Rails.root if [:force_enabled] config[:disabled] = false config[:disabled_for] = [] config[:disabled_in] = [] else config[:disabled] ||= false config[:disabled_for] ||= ['rake', 'script/generate'] config[:disabled_in] ||= ['test'] end config[:disabled] = false if config[:disabled] == 'false' config[:master_file] ||= Rails.root.join "tmp", "pids", "daemonz.master.pid" config[:logger] &&= [:override_logger] self.configure_logger if self.disabled? config[:is_master] = false else config[:is_master] = Daemonz.claim_master end end |
.configure_daemons ⇒ Object
process the daemon configuration
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 |
# File 'lib/daemonz/config.rb', line 90 def self.configure_daemons @daemons = [] config[:daemons].each do |name, daemon_config| next if daemon_config[:disabled] daemon = { :name => name } # compute the daemon startup / stop commands ['start', 'stop'].each do |command| daemon_binary = daemon_config[:binary] || daemon_config["#{command}_binary".to_sym] if daemon_config[:absolute_binary] daemon_path = `which #{daemon_binary}`.strip unless daemon_config[:kill_patterns] logger.error "Daemonz ignoring #{name}; using an absolute binary path but no custom process kill patterns" break end else daemon_path = File.join config[:root_path], daemon_binary || '' end unless daemon_binary and File.exists? daemon_path logger.error "Daemonz ignoring #{name}; the #{command} file is missing" break end unless daemon_config[:absolute_binary] begin binary_perms = File.stat(daemon_path).mode if binary_perms != (binary_perms | 0111) File.chmod(binary_perms | 0111, daemon_path) end rescue Exception => e # chmod might fail due to lack of permissions logger.error "Daemonz failed to make #{name} binary executable - #{e.class.name}: #{e}\n" logger.info e.backtrace.join("\n") + "\n" end end daemon_args = daemon_config[:args] || daemon_config["#{command}_args".to_sym] daemon_cmdline = "#{daemon_path} #{daemon_args}" daemon[command.to_sym] = {:path => daemon_path, :cmdline => daemon_cmdline} end next unless daemon[:stop] # kill patterns daemon[:kill_patterns] = daemon_config[:kill_patterns] || [daemon[:start][:path]] # pass-through params daemon[:pids] = daemon_config[:pids] unless daemon[:pids] logger.error "Daemonz ignoring #{name}; no pid file pattern specified" next end daemon[:delay_before_kill] = daemon_config[:delay_before_kill] || 0.2 daemon[:start_order] = daemon_config[:start_order] @daemons << daemon end # sort by start_order, then by name @daemons.sort! do |a, b| if a[:start_order] if b[:start_order] if a[:start_order] != b[:start_order] next a[:start_order] <=> b[:start_order] else next a[:name] <=> b[:name] end else next 1 end else next a[:name] <=> b[:name] end end end |
.configure_logger ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/daemonz/logging.rb', line 8 def self.configure_logger case config[:logger] when 'stdout' @logger = Logger.new(STDOUT) @logger.level = Logger::DEBUG when 'stderr' @logger = Logger.new(STDERR) @logger.level = Logger::DEBUG when 'rails' @logger = Rails.logger else @logger = Rails.logger end end |
.disabled? ⇒ Boolean
compute whether daemonz should be enabled or not
13 14 15 16 |
# File 'lib/daemonz/config.rb', line 13 def self.disabled? return config[:cached_disabled] if config.has_key? :cached_disabled config[:cached_disabled] = disabled_without_cache! end |
.disabled_without_cache! ⇒ Object
18 19 20 21 22 23 24 |
# File 'lib/daemonz/config.rb', line 18 def self.disabled_without_cache! return true if config[:disabled] return true if config[:disabled_in].include? Rails.env.to_s config[:disabled_for].any? do |suffix| suffix == $0[-suffix.length, suffix.length] end end |
.grab_master_lock ⇒ Object
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 |
# File 'lib/daemonz/master.rb', line 12 def self.grab_master_lock loop do File.open(config[:master_file], File::CREAT | File::RDWR) do |f| if f.flock File::LOCK_EX lock_data = f.read lock_data = lock_data[lock_data.index(/\d/), lock_data.length] if lock_data.index /\d/ master = lock_data.split("\n", 2) if master.length == 2 master_pid = master[0].to_i master_cmdline = master[1] if master_pid != 0 master_pinfo = process_info(master_pid) return master_pid if master_pinfo and master_pinfo[:cmdline] == master_cmdline logger.info "Old master (PID #{master_pid}) died; breaking master lock" end end f.truncate 0 f.write "#{$PID}\n#{process_info($PID)[:cmdline]}" f.flush return nil end end end end |
.kill_process_set(kill_script, pid_patterns, process_patterns, options = {}) ⇒ Object
Complex procedure for killing a process or a bunch of process replicas kill_command is the script that’s supposed to kill the process / processes (tried first) pid_patters are globs identifying PID files (a file can match any of the patterns) process_patterns are strings that should show on a command line (a process must match all) options:
:verbose - log what gets killed
:script_delay - the amount of seconds to sleep after launching the kill script
:force_script - the kill script is executed even if there are no PID files
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 |
# File 'lib/daemonz/killer.rb', line 10 def self.kill_process_set(kill_script, pid_patterns, process_patterns, = {}) # Phase 1: kill order (only if there's a PID file) pid_patterns = [pid_patterns] unless pid_patterns.kind_of? Enumerable unless [:force_script] pid_files = pid_patterns.map { |pattern| Dir.glob(pattern) }.flatten end if [:force_script] or !(pid_files.empty? or kill_script.nil?) logger.info "Issuing kill order: #{kill_script}\n" if [:verbose] unless kill_script.nil? child = POSIX::Spawn::Child.new kill_script if !child.success? and [:verbose] exit_code = child.status.exitstatus logger.warn "Kill order failed with exit code #{exit_code}" end end deadline_time = Time.now + ([:script_delay] || 0.5) while Time.now < deadline_time pid_files = pid_patterns.map { |pattern| Dir.glob(pattern) }.flatten break if pid_files.empty? sleep 0.05 end end # Phase 2: look through PID files and issue kill orders pinfo = process_info() pid_files = pid_patterns.map { |pattern| Dir.glob(pattern) }.flatten pid_files.each do |fname| begin pid = File.open(fname, 'r') { |f| f.read.strip! } process_cmdline = pinfo[pid][:cmdline] # avoid killing innocent victims if pinfo[pid].nil? or process_patterns.all? { |pattern| process_cmdline.index pattern } logger.warn "Killing #{pid}: #{process_cmdline}" if [:verbose] Process.kill 'TERM', pid.to_i end rescue # just in case the file gets wiped before we see it end begin logger.warn "Deleting #{fname}" if [:verbose] File.delete fname if File.exists? fname rescue # prevents crashing if the file is wiped after we call exists? end end # Phase 3: look through the process table and kill anything that looks good pinfo = process_info() pinfo.each do |pid, info| next unless process_patterns.all? { |pattern| info[:cmdline].index pattern } logger.warn "Killing #{pid}: #{pinfo[pid][:cmdline]}" if [:verbose] Process.kill 'TERM', pid.to_i end end |
.load_configuration(config_file) ⇒ Object
load and parse the config file
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 |
# File 'lib/daemonz/config.rb', line 54 def self.load_configuration(config_file) if File.exist? config_file file_contents = File.read config_file erb_result = ERB.new(file_contents).result @config = YAML.load erb_result @config[:daemons] ||= {} config_dir = File.join(File.dirname(config_file), 'daemonz') if File.exist? config_dir Dir.entries(config_dir).each do |entry| next unless entry =~ /^\w/ # Avoid temporary files. daemons_file = File.join(config_dir, entry) next unless File.file? daemons_file file_contents = File.read daemons_file erb_result = ERB.new(file_contents).result daemons = YAML.load erb_result daemons.keys.each do |daemon| if @config[:daemons].has_key? daemon logger.warn "Daemonz daemon file #{entry} overwrites daemon #{daemon} defined in daemonz.yml" end @config[:daemons][daemon] = daemons[daemon] end end end else logger.warn "Daemonz configuration not found - #{config_file}" @config = { :disabled => true } end end |
.logger ⇒ Object
4 5 6 |
# File 'lib/daemonz/logging.rb', line 4 def self.logger @logger || Rails.logger end |
.process_info(pid = nil) ⇒ Object
returns information about a process or all the running processes
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/daemonz/process.rb', line 55 def self.process_info(pid = nil) info = Hash.new Daemonz::ProcTable.ps.each do |process| item = { :cmdline => process.cmdline, :pid => process.pid.to_s } if pid.nil? info[process.pid.to_s] = item else return item if item[:pid].to_s == pid.to_s end end if pid.nil? return info else return nil end end |
.release_master_lock ⇒ Object
4 5 6 7 8 9 10 |
# File 'lib/daemonz/master.rb', line 4 def self.release_master_lock if File.exist? config[:master_file] File.delete config[:master_file] else logger.warn "Master lock removed by someone else" end end |
.safe_start(options = {}) ⇒ Object
Complete startup used by rake:start and at Rails plug-in startup.
15 16 17 18 19 20 21 22 23 |
# File 'lib/daemonz/manage.rb', line 15 def self.safe_start( = {}) daemonz_config = Rails.root.join 'config', 'daemonz.yml' Daemonz.configure daemonz_config, if Daemonz.config[:is_master] Daemonz.configure_daemons Daemonz.start_daemons! end end |
.safe_stop(options = {}) ⇒ Object
Complete shutdown used by rake:start and at Rails application exit.
26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/daemonz/manage.rb', line 26 def self.safe_stop( = {}) if [:configure] daemonz_config = Rails.root.join 'config', 'daemonz.yml' Daemonz.configure daemonz_config, end if Daemonz.config[:is_master] if [:configure] Daemonz.configure_daemons end Daemonz.stop_daemons! Daemonz.release_master_lock end end |
.start_daemon!(daemon) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/daemonz/manage.rb', line 63 def self.start_daemon!(daemon) logger.info "Daemonz killing any old instances of #{daemon[:name]}" # cleanup before we start kill_process_set daemon[:stop][:cmdline], daemon[:pids], daemon[:kill_patterns], :script_delay => daemon[:delay_before_kill], :verbose => true, :force_script => false logger.info "Daemonz starting #{daemon[:name]}: #{daemon[:start][:cmdline]}" child = POSIX::Spawn::Child.new daemon[:start][:cmdline] unless child.success? exit_code = child.status.exitstatus logger.warn "Daemonz start script for #{daemon[:name]} failed " + "with code #{exit_code}" end end |
.start_daemons! ⇒ Object
40 41 42 43 44 45 46 |
# File 'lib/daemonz/manage.rb', line 40 def self.start_daemons! if Daemonz.config[:async_start] Thread.new { start_daemons_sync } else start_daemons_sync end end |
.start_daemons_sync ⇒ Object
48 49 50 51 52 53 54 55 56 57 |
# File 'lib/daemonz/manage.rb', line 48 def self.start_daemons_sync begin @daemons.each { |daemon| start_daemon! daemon } rescue Exception => e logger.warn "Daemonz startup process failed. #{e.class}: #{e}\n" + e.backtrace.join("\n") ensure logger.flush end end |
.stop_daemon!(daemon) ⇒ Object
81 82 83 84 85 86 |
# File 'lib/daemonz/manage.rb', line 81 def self.stop_daemon!(daemon) kill_process_set daemon[:stop][:cmdline], daemon[:pids], daemon[:kill_patterns], :script_delay => daemon[:delay_before_kill], :verbose => true, :force_script => true end |
.stop_daemons! ⇒ Object
59 60 61 |
# File 'lib/daemonz/manage.rb', line 59 def self.stop_daemons! @daemons.reverse.each { |daemon| stop_daemon! daemon } end |
.with_daemons(logger = 'rails') ⇒ Object
Starts daemons, yields, stops daemons. Intended for tests.
5 6 7 8 9 10 11 12 |
# File 'lib/daemonz/manage.rb', line 5 def self.with_daemons(logger = 'rails') begin safe_start :force_enabled => true, :override_logger => logger yield ensure safe_stop :force_enabled => true end end |