daemonz 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Victor Costan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,68 @@
1
+ h1. Daemonz
2
+
3
+ bq. Automatically starts and stops the daemons in a Rails application.
4
+
5
+ h2. Installation
6
+
7
+ Install with:
8
+ <pre>
9
+ script/plugin install git://github.com/costan/daemonz.git
10
+ </pre>
11
+
12
+ You don't need to do anything in your code for daemonz to work. It will start
13
+ the daemons when your server starts, and stop them when your server exits. It
14
+ does work with multiple servers.
15
+
16
+ h2. Configuration
17
+
18
+ The main configuration file is config/daemonz.yml. Daemons are configured by
19
+ individual files in the config/daemonz directory. Configuration files are ran
20
+ through Erb, so you can go crazy.
21
+
22
+ Daemonz comes with a few configuration examples that can also be used as they
23
+ are, by removing the <code>:disabled: true</code> line. Please contribute your
24
+ configuration if you think others could use it.
25
+
26
+ h2. Daemon Generator
27
+
28
+ Daemonz includes a generator for a daemon intended to do background processing
29
+ inside a Rails environment. If you're writing your first daemon, give it a try.
30
+ The scaffolded code includes the configuration file, a daemon skeleton using the
31
+ <code>simple-daemon</code> gem, and an integration test skeleton.
32
+
33
+ <pre>
34
+ script/generate daemon YourDaemonName
35
+ </pre>
36
+
37
+ h2. Testing
38
+
39
+ You can test your daemonz configuration with rake, as shown below. Keep in mind
40
+ that the Rake tasks are only provided for testing, and you should not use them
41
+ while your Rails application is running. Nothing bad should happen if you run
42
+ the tasks by mistake, but things may go astray if you mix the Rake tasks with
43
+ application starts and stops.
44
+ <pre>
45
+ rake daemons:start # Starts your daemons (for testing only)
46
+ rake daemons:stop # Stops your daemons (for testing only)
47
+ </pre>
48
+
49
+ Using the default configuration, daemons are not running during tests. The
50
+ snippet below shows how you can have daemons running during a specific test.
51
+ <notextile>
52
+ <pre>
53
+ class DaemonsTest < ActionController::IntegrationTest
54
+ # If this isn't here, Rails runs the entire test in a transaction, so daemons'
55
+ # database changes aren't visible. Also, if you share the database with
56
+ # daemons, sqlite won't cut it.
57
+ self.use_transactional_fixtures = false
58
+
59
+ test "something needing daemons" do
60
+ Daemonz.with_daemons do
61
+ # daemons will be alive while this code is executed
62
+ end
63
+ end
64
+ end
65
+ </pre>
66
+ </notextile>
67
+
68
+ p. Copyright (c) 2008 Victor Costan, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the daemonz plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the daemonz plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Daemonz'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,87 @@
1
+ # Configuration file for the daemonz plugin.
2
+ #
3
+ # For documentation and examples, refer to the original template in
4
+ # vendor/daemonz/config_template.yml
5
+
6
+ ---
7
+ # Descriptions of the daemons to be managed by daemonz.
8
+ #
9
+ # daemonz starts the daemons according to the alphabetic order of their names.
10
+ # start_order can be used to override this order. Daemons will be stored in the
11
+ # reverse order of their starting order.
12
+ #
13
+ # daemonz likes to ensure that multiple instances of a daemon don't run at the
14
+ # same time, as this can be fatal for daemons with on-disk state, like ferret.
15
+ # So daemonz ensures that a daemon is dead before starting it, and right after
16
+ # stopping it. This is achieved using the following means:
17
+ # * Stop commands: first, daemonz uses the stop command supplied in the daemon
18
+ # configuration
19
+ # * PID files: if the daemon has .pid files, daemonz tries to read the file
20
+ # and find the corresponding processes, then tree-kills them
21
+ # * Process table: if possible, daemonz dumps the process table, looks for the
22
+ # processes that look like the daemon, and tree-kills them
23
+ # * Pattern matching: processes whose command lines have the same arguments as
24
+ # those given to daemon "look like" that daemon
25
+ # * Tree killing: a daemon is killed by killing its main process, together
26
+ # with all processes descending from that process; a process
27
+ # is first sent SIGTERM and, if it's still alive after a
28
+ # couple of seconds, it's sent a SIGKILL
29
+ :daemons:
30
+ # Example: configuring ferret.
31
+ ferret:
32
+ # Ferret uses different binaries to be started and stopped.
33
+ :start_binary: script/ferret_server
34
+ :stop_binary: script/ferret_server
35
+ # The arguments to be given to the start and stop commands.
36
+ # Note that this file is processed with Erb, like your views.
37
+ :start_args: -e <%= RAILS_ENV %> start
38
+ :stop_args: -e <%= RAILS_ENV %> stop
39
+ # Time to wait after sending the stop command, before killing the daemon.
40
+ :delay_before_kill: 0.35
41
+ # Pattern for the PID file(s) used by the daemon.
42
+ :pids: tmp/pids/ferret*.pid
43
+ # daemonz will ignore this daemon configuration when this flag is true
44
+ :disabled: true
45
+ starling:
46
+ # The same binary is used to start and stop starling.
47
+ :binary: starling
48
+ # The binary name will not be merged with root_path.
49
+ :absolute_binary: true
50
+ :start_args: -d -h 127.0.0.1 -p 16020 -P <%= RAILS_ROOT %>/tmp/pids/starling.pid -q <%= RAILS_ROOT %>/tmp -L <%= RAILS_ROOT %>/log/starling.log
51
+ # No arguments are needed to stop starling.
52
+ :stop_args: ''
53
+ # Pattern for the PID file(s) used by the daemon.
54
+ :pids: tmp/pids/starling*.pid
55
+ # Override for the patterns used to identify the daemon's processes.
56
+ :kill_patterns: <%= RAILS_ROOT %>/log/starling.log
57
+ # Time to wait after sending the stop command, before killing the daemon.
58
+ :delay_before_kill: 0.2
59
+ # Override the daemon startup order. Starling consumer daemons should have
60
+ # their start_order set to 2, so starling is running when they start.
61
+ :start_order: 1
62
+ # daemonz will ignore this daemon configuration when this flag is true
63
+ :disabled: true
64
+
65
+ # The base path for daemon binaries specified in binary, start_binary and
66
+ # stop_binary.
67
+ :root_path: <%= RAILS_ROOT %>
68
+
69
+ # Where daemonz should log - set to stdout, stderr, or rails.
70
+ :logger: stdout
71
+
72
+ # Set to true to completely disable daemonz, and not load any plugins.
73
+ :disabled: false
74
+
75
+ # daemonz is loaded every time the Rails framework is loaded. Sometimes
76
+ # (e.g. when performing migrations) daemons aren't required, so we shouldn't
77
+ # have to wait for the few seconds it takes to start / stop daemons.
78
+
79
+ # Daemons will not be started / stopped when the name of the binary that's
80
+ # loading Rails ($0) is one of the following.
81
+ :disabled_for:
82
+ - 'rake'
83
+ - 'script/generate'
84
+
85
+ # Daemons will not be started for the following environments.
86
+ :disabled_in:
87
+ - 'test'
data/daemonz.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{daemonz}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Victor Costan"]
12
+ s.date = %q{2010-04-18}
13
+ s.email = %q{victor@costan.us}
14
+ s.extra_rdoc_files = [
15
+ "README.textile"
16
+ ]
17
+ s.files = [
18
+ "MIT-LICENSE",
19
+ "README.textile",
20
+ "Rakefile",
21
+ "config_template.yml",
22
+ "daemonz.gemspec",
23
+ "init.rb",
24
+ "lib/daemonz.rb",
25
+ "lib/daemonz/config.rb",
26
+ "lib/daemonz/killer.rb",
27
+ "lib/daemonz/logging.rb",
28
+ "lib/daemonz/manage.rb",
29
+ "lib/daemonz/master.rb",
30
+ "lib/daemonz/process.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/costan/daemonz}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.6}
36
+ s.summary = %q{Automatically starts and stops the daemons in a Rails application}
37
+ s.test_files = [
38
+ "test/daemonz_test.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_runtime_dependency(%q<simple-daemon>, [">= 0"])
47
+ s.add_runtime_dependency(%q<zerg_support>, [">= 0"])
48
+ else
49
+ s.add_dependency(%q<simple-daemon>, [">= 0"])
50
+ s.add_dependency(%q<zerg_support>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<simple-daemon>, [">= 0"])
54
+ s.add_dependency(%q<zerg_support>, [">= 0"])
55
+ end
56
+ end
57
+
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'daemonz'
2
+
3
+ Daemonz.safe_start
4
+ at_exit do
5
+ Daemonz.safe_stop unless Daemonz.keep_daemons_at_exit
6
+ end
@@ -0,0 +1,163 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module Daemonz
5
+ class << self
6
+ attr_reader :config
7
+
8
+ # Set by the rake tasks.
9
+ attr_accessor :keep_daemons_at_exit
10
+ end
11
+
12
+ # compute whether daemonz should be enabled or not
13
+ def self.disabled?
14
+ return config[:cached_disabled] if config.has_key? :cached_disabled
15
+ config[:cached_disabled] = disabled_without_cache!
16
+ end
17
+
18
+ def self.disabled_without_cache!
19
+ return true if config[:disabled]
20
+ return true if config[:disabled_in].include? RAILS_ENV
21
+ config[:disabled_for].any? do |suffix|
22
+ suffix == $0[-suffix.length, suffix.length]
23
+ end
24
+ end
25
+
26
+ # figure out the plugin's configuration
27
+ def self.configure(config_file, options = {})
28
+ load_configuration config_file
29
+
30
+ config[:root_path] ||= RAILS_ROOT
31
+ if options[:force_enabled]
32
+ config[:disabled] = false
33
+ config[:disabled_for] = []
34
+ config[:disabled_in] = []
35
+ else
36
+ config[:disabled] ||= false
37
+ config[:disabled_for] ||= ['rake', 'script/generate']
38
+ config[:disabled_in] ||= ['test']
39
+ end
40
+ config[:disabled] = false if config[:disabled] == 'false'
41
+ config[:master_file] ||= File.join RAILS_ROOT, "tmp", "pids", "daemonz.master.pid"
42
+
43
+ config[:logger] &&= options[:override_logger]
44
+ self.configure_logger
45
+
46
+ if self.disabled?
47
+ config[:is_master] = false
48
+ else
49
+ config[:is_master] = Daemonz.claim_master
50
+ end
51
+ end
52
+
53
+ # load and parse the config file
54
+ def self.load_configuration(config_file)
55
+ if File.exist? config_file
56
+ file_contents = File.read config_file
57
+ erb_result = ERB.new(file_contents).result
58
+ @config = YAML.load erb_result
59
+ @config[:daemons] ||= {}
60
+
61
+ config_dir = File.join(File.dirname(config_file), 'daemonz')
62
+ if File.exist? config_dir
63
+ Dir.entries(config_dir).each do |entry|
64
+ daemons_file = File.join(config_dir, entry)
65
+ next unless File.file? daemons_file
66
+
67
+ file_contents = File.read daemons_file
68
+ erb_result = ERB.new(file_contents).result
69
+ daemons = YAML.load erb_result
70
+ daemons.keys.each do |daemon|
71
+ if @config[:daemons].has_key? daemon
72
+ logger.warn "Daemonz daemon file #{entry} overwrites daemon #{daemon} defined in daemonz.yml"
73
+ end
74
+ @config[:daemons][daemon] = daemons[daemon]
75
+ end
76
+ end
77
+ end
78
+ else
79
+ logger.warn "Daemonz configuration not found - #{config_file}"
80
+ @config = {}
81
+ end
82
+ end
83
+
84
+ class << self
85
+ attr_reader :daemons
86
+ end
87
+
88
+ # process the daemon configuration
89
+ def self.configure_daemons
90
+ @daemons = []
91
+ config[:daemons].each do |name, daemon_config|
92
+ next if daemon_config[:disabled]
93
+ daemon = { :name => name }
94
+
95
+ # compute the daemon startup / stop commands
96
+ ['start', 'stop'].each do |command|
97
+ daemon_binary = daemon_config[:binary] || daemon_config["#{command}_binary".to_sym]
98
+ if daemon_config[:absolute_binary]
99
+ daemon_path = `which #{daemon_binary}`.strip
100
+ unless daemon_config[:kill_patterns]
101
+ logger.error "Daemonz ignoring #{name}; using an absolute binary path but no custom process kill patterns"
102
+ break
103
+ end
104
+ else
105
+ daemon_path = File.join config[:root_path], daemon_binary || ''
106
+ end
107
+ unless daemon_binary and File.exists? daemon_path
108
+ logger.error "Daemonz ignoring #{name}; the #{command} file is missing"
109
+ break
110
+ end
111
+
112
+ unless daemon_config[:absolute_binary]
113
+ begin
114
+ binary_perms = File.stat(daemon_path).mode
115
+ if binary_perms != (binary_perms | 0111)
116
+ File.chmod(binary_perms | 0111, daemon_path)
117
+ end
118
+ rescue Exception => e
119
+ # chmod might fail due to lack of permissions
120
+ logger.error "Daemonz failed to make #{name} binary executable - #{e.class.name}: #{e}\n"
121
+ logger.info e.backtrace.join("\n") + "\n"
122
+ end
123
+ end
124
+
125
+ daemon_args = daemon_config[:args] || daemon_config["#{command}_args".to_sym]
126
+ daemon_cmdline = "#{daemon_path} #{daemon_args}"
127
+ daemon[command.to_sym] = {:path => daemon_path, :cmdline => daemon_cmdline}
128
+ end
129
+ next unless daemon[:stop]
130
+
131
+ # kill patterns
132
+ daemon[:kill_patterns] = daemon_config[:kill_patterns] || [daemon[:start][:path]]
133
+
134
+ # pass-through params
135
+ daemon[:pids] = daemon_config[:pids]
136
+ unless daemon[:pids]
137
+ logger.error "Daemonz ignoring #{name}; no pid file pattern specified"
138
+ next
139
+ end
140
+ daemon[:delay_before_kill] = daemon_config[:delay_before_kill] || 0.2
141
+ daemon[:start_order] = daemon_config[:start_order]
142
+
143
+ @daemons << daemon
144
+ end
145
+
146
+ # sort by start_order, then by name
147
+ @daemons.sort! do |a, b|
148
+ if a[:start_order]
149
+ if b[:start_order]
150
+ if a[:start_order] != b[:start_order]
151
+ next a[:start_order] <=> b[:start_order]
152
+ else
153
+ next a[:name] <=> b[:name]
154
+ end
155
+ else
156
+ next 1
157
+ end
158
+ else
159
+ next a[:name] <=> b[:name]
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,64 @@
1
+ require 'English'
2
+
3
+ module Daemonz
4
+ # Complex procedure for killing a process or a bunch of process replicas
5
+ # kill_command is the script that's supposed to kill the process / processes (tried first)
6
+ # pid_patters are globs identifying PID files (a file can match any of the patterns)
7
+ # process_patterns are strings that should show on a command line (a process must match all)
8
+ # options:
9
+ # :verbose - log what gets killed
10
+ # :script_delay - the amount of seconds to sleep after launching the kill script
11
+ # :force_script - the kill script is executed even if there are no PID files
12
+ def self.kill_process_set(kill_script, pid_patterns, process_patterns, options = {})
13
+ # Phase 1: kill order (only if there's a PID file)
14
+ pid_patterns = [pid_patterns] unless pid_patterns.kind_of? Enumerable
15
+ unless options[:force_script]
16
+ pid_files = pid_patterns.map { |pattern| Dir.glob(pattern) }.flatten
17
+ end
18
+ if options[:force_script] or !(pid_files.empty? or kill_script.nil?)
19
+ logger.info "Issuing kill order: #{kill_script}\n" if options[:verbose]
20
+ success = Kernel.system kill_script unless kill_script.nil?
21
+ if !success and options[:verbose]
22
+ logger.warn "Kill order failed with exit code #{$CHILD_STATUS.exitstatus}"
23
+ end
24
+
25
+ deadline_time = Time.now + (options[:script_delay] || 0.5)
26
+ while Time.now < deadline_time
27
+ pid_files = pid_patterns.map { |pattern| Dir.glob(pattern) }.flatten
28
+ break if pid_files.empty?
29
+ sleep 0.05
30
+ end
31
+ end
32
+
33
+ # Phase 2: look through PID files and issue kill orders
34
+ pinfo = process_info()
35
+ pid_files = pid_patterns.map { |pattern| Dir.glob(pattern) }.flatten
36
+ pid_files.each do |fname|
37
+ begin
38
+ pid = File.open(fname, 'r') { |f| f.read.strip! }
39
+ process_cmdline = pinfo[pid][:cmdline]
40
+ # avoid killing innocent victims
41
+ if pinfo[pid].nil? or process_patterns.all? { |pattern| process_cmdline.index pattern }
42
+ logger.warn "Killing #{pid}: #{process_cmdline}" if options[:verbose]
43
+ Process.kill 'TERM', pid.to_i
44
+ end
45
+ rescue
46
+ # just in case the file gets wiped before we see it
47
+ end
48
+ begin
49
+ logger.warn "Deleting #{fname}" if options[:verbose]
50
+ File.delete fname if File.exists? fname
51
+ rescue
52
+ # prevents crashing if the file is wiped after we call exists?
53
+ end
54
+ end
55
+
56
+ # Phase 3: look through the process table and kill anything that looks good
57
+ pinfo = process_info()
58
+ pinfo.each do |pid, info|
59
+ next unless process_patterns.all? { |pattern| info[:cmdline].index pattern }
60
+ logger.warn "Killing #{pid}: #{pinfo[pid][:cmdline]}" if options[:verbose]
61
+ Process.kill 'TERM', pid.to_i
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ module Daemonz
2
+ @logger = RAILS_DEFAULT_LOGGER
3
+ class <<self
4
+ attr_reader :logger
5
+ end
6
+
7
+ def self.configure_logger
8
+ case config[:logger]
9
+ when 'stdout'
10
+ @logger = Logger.new(STDOUT)
11
+ @logger.level = Logger::DEBUG
12
+ when 'stderr'
13
+ @logger = Logger.new(STDERR)
14
+ @logger.level = Logger::DEBUG
15
+ when 'rails'
16
+ @logger = RAILS_DEFAULT_LOGGER
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ require 'English'
2
+
3
+ module Daemonz
4
+ # Starts daemons, yields, stops daemons. Intended for tests.
5
+ def self.with_daemons(logger = 'rails')
6
+ begin
7
+ safe_start :force_enabled => true, :override_logger => logger
8
+ yield
9
+ ensure
10
+ safe_stop :force_enabled => true
11
+ end
12
+ end
13
+
14
+ # Complete startup used by rake:start and at Rails plug-in startup.
15
+ def self.safe_start(options = {})
16
+ daemonz_config = File.join(RAILS_ROOT, 'config', 'daemonz.yml')
17
+ Daemonz.configure daemonz_config, options
18
+
19
+ if Daemonz.config[:is_master]
20
+ Daemonz.configure_daemons
21
+ Daemonz.start_daemons!
22
+ end
23
+ end
24
+
25
+ # Complete shutdown used by rake:start and at Rails application exit.
26
+ def self.safe_stop(options = {})
27
+ if options[:configure]
28
+ daemonz_config = File.join(RAILS_ROOT, 'config', 'daemonz.yml')
29
+ Daemonz.configure daemonz_config, options
30
+ end
31
+ if Daemonz.config[:is_master]
32
+ if options[:configure]
33
+ Daemonz.configure_daemons
34
+ end
35
+ Daemonz.stop_daemons!
36
+ Daemonz.release_master_lock
37
+ end
38
+ end
39
+
40
+ def self.start_daemons!
41
+ @daemons.each { |daemon| start_daemon! daemon }
42
+ end
43
+
44
+ def self.stop_daemons!
45
+ @daemons.reverse.each { |daemon| stop_daemon! daemon }
46
+ end
47
+
48
+ def self.start_daemon!(daemon)
49
+ # cleanup before we start
50
+ kill_process_set daemon[:stop][:cmdline], daemon[:pids],
51
+ daemon[:kill_patterns],
52
+ :script_delay => daemon[:delay_before_kill],
53
+ :verbose => true, :force_script => false
54
+
55
+ logger.info "Daemonz starting #{daemon[:name]}: #{daemon[:start][:cmdline]}"
56
+ success = Kernel.system daemon[:start][:cmdline]
57
+ unless success
58
+ logger.warn "Daemonz start script for #{daemon[:name]} failed " +
59
+ "with code #{$CHILD_STATUS.exitstatus}"
60
+ end
61
+ end
62
+
63
+ def self.stop_daemon!(daemon)
64
+ kill_process_set daemon[:stop][:cmdline], daemon[:pids],
65
+ daemon[:kill_patterns],
66
+ :script_delay => daemon[:delay_before_kill],
67
+ :verbose => true, :force_script => true
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ require 'English'
2
+
3
+ module Daemonz
4
+ def self.release_master_lock
5
+ if File.exist? config[:master_file]
6
+ File.delete config[:master_file]
7
+ else
8
+ logger.warn "Master lock removed by someone else"
9
+ end
10
+ end
11
+
12
+ def self.grab_master_lock
13
+ loop do
14
+ File.open(config[:master_file], File::CREAT | File::RDWR) do |f|
15
+ if f.flock File::LOCK_EX
16
+ lock_data = f.read
17
+ lock_data = lock_data[lock_data.index(/\d/), lock_data.length] if lock_data.index /\d/
18
+ master = lock_data.split("\n", 2)
19
+
20
+ if master.length == 2
21
+ master_pid = master[0].to_i
22
+ master_cmdline = master[1]
23
+ if master_pid != 0
24
+ master_pinfo = process_info(master_pid)
25
+ return master_pid if master_pinfo and master_pinfo[:cmdline] == master_cmdline
26
+
27
+ logger.info "Old master (PID #{master_pid}) died; breaking master lock"
28
+ end
29
+ end
30
+
31
+ f.truncate 0
32
+ f.write "#{$PID}\n#{process_info($PID)[:cmdline]}"
33
+ f.flush
34
+ return nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # attempts to claim the master lock
41
+ def self.claim_master
42
+ loop do
43
+ begin
44
+ # try to grab that lock
45
+ master_pid = grab_master_lock
46
+ if master_pid
47
+ logger.info "Daemonz in slave mode; PID #{master_pid} has master lock"
48
+ return false
49
+ else
50
+ logger.info "Daemonz grabbed master lock"
51
+ return true
52
+ end
53
+ #rescue Exception => e
54
+ # logger.warn "Daemonz mastering failed: #{e.class.name} - #{e}"
55
+ # logger.info "Retrying daemonz mastering"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ # Mocks the sys-proctable gem using ps.
2
+ #
3
+ # This is useful even if sys-proctable is available, because it may fail for
4
+ # random reasons.
5
+ module Daemonz::ProcTable
6
+ class ProcInfo
7
+ def initialize(pid, cmdline)
8
+ @pid = pid
9
+ @cmdline = cmdline
10
+ end
11
+ attr_reader :pid, :cmdline
12
+ end
13
+
14
+ def self.ps_emulation
15
+ retval = []
16
+ ps_output = `ps ax`
17
+ ps_output.each_line do |pline|
18
+ pdata = pline.split(nil, 5)
19
+ pinfo = ProcInfo.new(pdata[0].strip, pdata[4].strip)
20
+ retval << pinfo
21
+ end
22
+ return retval
23
+ end
24
+ end
25
+
26
+ begin
27
+ require 'sys/proctable'
28
+ if Sys::ProcTable::VERSION == '0.7.6'
29
+ raise LoadError, 'Buggy sys/proctable, emulate'
30
+ end
31
+
32
+ module Daemonz::ProcTable
33
+ def self.ps
34
+ # We don't use ps_emulation all the time because sys-proctable is
35
+ # faster. We only pay the performance penalty when sys-proctable fails.
36
+ begin
37
+ Sys::ProcTable.ps
38
+ rescue Exception
39
+ self.ps_emulation
40
+ end
41
+ end
42
+ end
43
+ rescue LoadError
44
+ # The accelerated version is not available, use the slow version all the time.
45
+
46
+ module Daemonz::ProcTable
47
+ def self.ps
48
+ self.ps_emulation
49
+ end
50
+ end
51
+ end
52
+
53
+ module Daemonz
54
+ # returns information about a process or all the running processes
55
+ def self.process_info(pid = nil)
56
+ info = Hash.new
57
+
58
+ Daemonz::ProcTable.ps.each do |process|
59
+ item = { :cmdline => process.cmdline, :pid => process.pid.to_s }
60
+
61
+ if pid.nil?
62
+ info[process.pid.to_s] = item
63
+ else
64
+ return item if item[:pid].to_s == pid.to_s
65
+ end
66
+ end
67
+
68
+ if pid.nil?
69
+ return info
70
+ else
71
+ return nil
72
+ end
73
+ end
74
+ end
data/lib/daemonz.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Daemonz
2
+
3
+ end
4
+
5
+ require 'daemonz/config.rb'
6
+ require 'daemonz/killer.rb'
7
+ require 'daemonz/logging.rb'
8
+ require 'daemonz/manage.rb'
9
+ require 'daemonz/master.rb'
10
+ require 'daemonz/process.rb'
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+
3
+ class DaemonzTest < Test::Unit::TestCase
4
+ # Replace this with your real tests.
5
+ def test_this_plugin
6
+ flunk
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: daemonz
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
+ platform: ruby
11
+ authors:
12
+ - Victor Costan
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-18 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: simple-daemon
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: zerg_support
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ description:
45
+ email: victor@costan.us
46
+ executables: []
47
+
48
+ extensions: []
49
+
50
+ extra_rdoc_files:
51
+ - README.textile
52
+ files:
53
+ - MIT-LICENSE
54
+ - README.textile
55
+ - Rakefile
56
+ - config_template.yml
57
+ - daemonz.gemspec
58
+ - init.rb
59
+ - lib/daemonz.rb
60
+ - lib/daemonz/config.rb
61
+ - lib/daemonz/killer.rb
62
+ - lib/daemonz/logging.rb
63
+ - lib/daemonz/manage.rb
64
+ - lib/daemonz/master.rb
65
+ - lib/daemonz/process.rb
66
+ has_rdoc: true
67
+ homepage: http://github.com/costan/daemonz
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --charset=UTF-8
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.6
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Automatically starts and stops the daemons in a Rails application
96
+ test_files:
97
+ - test/daemonz_test.rb