daemonz 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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