daemons 1.1.9 → 1.4.1

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/README.md +207 -0
  4. data/Releases +85 -24
  5. data/examples/call/call.rb +13 -16
  6. data/examples/call/call_monitor.rb +13 -17
  7. data/examples/daemonize/daemonize.rb +4 -8
  8. data/examples/run/ctrl_crash.rb +0 -1
  9. data/examples/run/ctrl_custom_logfiles.rb +18 -0
  10. data/examples/run/ctrl_exec.rb +0 -1
  11. data/examples/run/ctrl_exit.rb +0 -1
  12. data/examples/run/ctrl_keep_pid_files.rb +1 -3
  13. data/examples/run/ctrl_monitor.rb +0 -1
  14. data/examples/run/ctrl_monitor_multiple.rb +17 -0
  15. data/examples/run/ctrl_monitor_nocrash.rb +15 -0
  16. data/examples/run/ctrl_multiple.rb +0 -1
  17. data/examples/run/ctrl_ontop.rb +0 -1
  18. data/examples/run/ctrl_optionparser.rb +5 -7
  19. data/examples/run/ctrl_proc.rb +8 -9
  20. data/examples/run/ctrl_proc_multiple.rb +4 -6
  21. data/examples/run/ctrl_proc_rand.rb +2 -4
  22. data/examples/run/ctrl_proc_simple.rb +0 -1
  23. data/examples/run/myserver.rb +0 -1
  24. data/examples/run/myserver_crashing.rb +5 -5
  25. data/examples/run/myserver_exiting.rb +2 -2
  26. data/examples/run/myserver_hanging.rb +4 -5
  27. data/examples/run/myserver_slowstop.rb +5 -6
  28. data/lib/daemons/application.rb +235 -229
  29. data/lib/daemons/application_group.rb +115 -100
  30. data/lib/daemons/change_privilege.rb +2 -4
  31. data/lib/daemons/cmdline.rb +75 -62
  32. data/lib/daemons/controller.rb +36 -54
  33. data/lib/daemons/daemonize.rb +74 -75
  34. data/lib/daemons/etc_extension.rb +3 -4
  35. data/lib/daemons/exceptions.rb +11 -13
  36. data/lib/daemons/monitor.rb +57 -77
  37. data/lib/daemons/pid.rb +26 -56
  38. data/lib/daemons/pidfile.rb +49 -44
  39. data/lib/daemons/pidmem.rb +5 -9
  40. data/lib/daemons/reporter.rb +54 -0
  41. data/lib/daemons/syslogio.rb +240 -0
  42. data/lib/daemons/version.rb +3 -0
  43. data/lib/daemons.rb +87 -77
  44. metadata +111 -46
  45. data/README +0 -214
  46. data/Rakefile +0 -90
  47. data/TODO +0 -2
  48. data/setup.rb +0 -1360
@@ -1,65 +1,63 @@
1
1
 
2
2
  module Daemons
3
3
  class ApplicationGroup
4
-
5
4
  attr_reader :app_name
6
5
  attr_reader :script
7
-
6
+
8
7
  attr_reader :monitor
9
-
10
- #attr_reader :controller
11
-
8
+
12
9
  attr_reader :options
13
-
10
+
14
11
  attr_reader :applications
15
-
12
+
16
13
  attr_accessor :controller_argv
17
14
  attr_accessor :app_argv
18
-
15
+
19
16
  attr_accessor :dir_mode
20
17
  attr_accessor :dir
21
-
18
+
22
19
  # true if the application is supposed to run in multiple instances
23
20
  attr_reader :multiple
24
-
25
-
21
+
26
22
  def initialize(app_name, options = {})
27
23
  @app_name = app_name
28
24
  @options = options
29
-
30
- if options[:script]
31
- @script = File.expand_path(options[:script])
25
+
26
+ if @options[:script]
27
+ @script = File.expand_path(@options[:script])
32
28
  end
33
-
34
- #@controller = controller
29
+
35
30
  @monitor = nil
36
-
37
- #options = controller.options
38
-
39
- @multiple = options[:multiple] || false
40
-
41
- @dir_mode = options[:dir_mode] || :script
42
- @dir = options[:dir] || ''
43
-
44
- @keep_pid_files = options[:keep_pid_files] || false
45
- @no_pidfiles = options[:no_pidfiles] || false
46
-
47
- #@applications = find_applications(pidfile_dir())
31
+
32
+ @multiple = @options[:multiple] || false
33
+
34
+ @dir_mode = @options[:dir_mode] || :script
35
+ ['dir'].each do |k|
36
+ @options[k] = File.expand_path(@options[k]) if @options.key?(k)
37
+ end
38
+ @dir = @options[:dir] || ''
39
+
40
+ @keep_pid_files = @options[:keep_pid_files] || false
41
+
42
+ @no_pidfiles = @options[:no_pidfiles] || false
43
+
44
+ @pid_delimiter = @options[:pid_delimiter]
45
+
48
46
  @applications = []
49
47
  end
50
-
48
+
51
49
  # Setup the application group.
52
50
  # Currently this functions calls <tt>find_applications</tt> which finds
53
51
  # all running instances of the application and populates the application array.
54
52
  #
55
53
  def setup
56
- @applications = find_applications(pidfile_dir())
54
+ @applications = find_applications(pidfile_dir)
57
55
  end
58
-
56
+
59
57
  def pidfile_dir
60
58
  PidFile.dir(@dir_mode, @dir, script)
61
- end
62
-
59
+ end
60
+
63
61
  def find_applications(dir)
64
62
  if @no_pidfiles
65
63
  return find_applications_by_app_name(app_name)
@@ -67,128 +65,145 @@ module Daemons
67
65
  return find_applications_by_pidfiles(dir)
68
66
  end
69
67
  end
70
-
68
+
71
69
  # TODO: identifiy the monitor process
72
70
  def find_applications_by_app_name(app_name)
73
71
  pids = []
74
-
72
+
75
73
  begin
76
- x = `ps auxw | grep -v grep | awk '{print $2, $11, $12}' | grep #{app_name}`
77
- if x && x.chomp!
78
- processes = x.split(/\n/).compact
79
- processes = processes.delete_if do |p|
80
- pid, name, add = p.split(/\s/)
81
- # We want to make sure that the first part of the process name matches
82
- # so that app_name matches app_name_22
83
-
84
- app_name != name[0..(app_name.length - 1)] and not add.include?(app_name)
74
+ x = `ps auxw | grep -v grep | awk '{print $2, $11, $12}' | grep #{app_name}`
75
+ if x && x.chomp!
76
+ processes = x.split(/\n/).compact
77
+ processes = processes.delete_if do |p|
78
+ _pid, name, add = p.split(/\s/)
79
+ # We want to make sure that the first part of the process name matches
80
+ # so that app_name matches app_name_22
81
+
82
+ app_name != name[0..(app_name.length - 1)] and not add.include?(app_name)
83
+ end
84
+ pids = processes.map { |p| p.split(/\s/)[0].to_i }
85
85
  end
86
- pids = processes.map {|p| p.split(/\s/)[0].to_i}
87
- end
88
86
  rescue ::Exception
89
87
  end
90
-
91
- return pids.map {|f|
88
+
89
+ pids.map do |f|
92
90
  app = Application.new(self, {}, PidMem.existing(f))
93
91
  setup_app(app)
94
92
  app
95
- }
93
+ end
96
94
  end
97
-
95
+
98
96
  def find_applications_by_pidfiles(dir)
99
- pid_files = PidFile.find_files(dir, app_name, ! @keep_pid_files)
100
-
101
- #pp pid_files
102
-
103
97
  @monitor = Monitor.find(dir, app_name + '_monitor')
104
-
105
- pid_files.reject! {|f| f =~ /_monitor.pid$/}
106
-
107
- return pid_files.map {|f|
98
+
99
+ reporter = Reporter.new(options)
100
+ pid_files = PidFile.find_files(dir, app_name, ! @keep_pid_files, @pid_delimiter) do |pid, file|
101
+ reporter.deleted_found_pidfile(pid, file)
102
+ end
103
+
104
+ pid_files.map do |f|
108
105
  app = Application.new(self, {}, PidFile.existing(f))
109
106
  setup_app(app)
110
107
  app
111
- }
108
+ end
112
109
  end
113
-
110
+
114
111
  def new_application(add_options = {})
115
- if @applications.size > 0 and not @multiple
112
+ if @applications.size > 0 && !@multiple
116
113
  if options[:force]
117
- @applications.delete_if {|a|
114
+ @applications.delete_if do |a|
118
115
  unless a.running?
119
116
  a.zap
120
117
  true
121
118
  end
122
- }
119
+ end
123
120
  end
124
-
125
- raise RuntimeException.new('there is already one or more instance(s) of the program running') unless @applications.empty?
121
+
122
+ fail RuntimeException.new('there is already one or more instance(s) of the program running') unless @applications.empty?
126
123
  end
127
-
124
+
128
125
  app = Application.new(self, add_options)
129
-
126
+
130
127
  setup_app(app)
131
-
128
+
132
129
  @applications << app
133
-
134
- return app
130
+
131
+ app
135
132
  end
136
-
133
+
137
134
  def setup_app(app)
138
135
  app.controller_argv = @controller_argv
139
136
  app.app_argv = @app_argv
137
+ if @options[:show_status_callback]
138
+ app.show_status_callback = @options[:show_status_callback]
139
+ end
140
140
  end
141
141
  private :setup_app
142
-
142
+
143
143
  def create_monitor(an_app)
144
- return if @monitor
145
-
146
- if options[:monitor]
147
- @monitor = Monitor.new(an_app)
144
+ if @monitor && options[:monitor]
145
+ @monitor.stop
146
+ @monitor = nil
147
+ end
148
148
 
149
- @monitor.start(@applications)
149
+ if options[:monitor]
150
+ opt = {}
151
+ opt[:monitor_interval] = options[:monitor_interval] if options[:monitor_interval]
152
+ @monitor = Monitor.new(an_app, opt)
153
+ @monitor.start(self)
150
154
  end
151
155
  end
152
-
156
+
153
157
  def start_all
154
158
  @monitor.stop if @monitor
155
159
  @monitor = nil
156
-
157
- @applications.each {|a|
158
- fork {
159
- a.start
160
- }
161
- }
160
+
161
+ pids = []
162
+ @applications.each do |a|
163
+ pids << fork do
164
+ a.start
165
+ end
166
+ end
167
+ pids.each { |pid| Process.waitpid(pid) }
162
168
  end
163
-
169
+
164
170
  def stop_all(no_wait = false)
165
- @monitor.stop if @monitor
166
-
171
+ if @monitor
172
+ @monitor.stop
173
+ @monitor = nil
174
+ setup
175
+ end
176
+
167
177
  threads = []
168
-
169
- @applications.each {|a|
178
+
179
+ @applications.each do |a|
170
180
  threads << Thread.new do
171
181
  a.stop(no_wait)
172
182
  end
173
- }
174
-
175
- threads.each {|t| t.join}
183
+ end
184
+
185
+ threads.each { |t| t.join }
176
186
  end
177
-
187
+
178
188
  def reload_all
179
- @applications.each {|a| a.reload}
189
+ @applications.each { |a| a.reload }
180
190
  end
181
191
 
182
192
  def zap_all
183
193
  @monitor.stop if @monitor
184
-
185
- @applications.each {|a| a.zap}
194
+
195
+ @applications.each { |a| a.zap }
186
196
  end
187
-
197
+
188
198
  def show_status
189
- @applications.each {|a| a.show_status}
199
+ @applications.each { |a| a.show_status }
190
200
  end
191
-
192
- end
193
201
 
202
+ # Check whether at least one of the applications in the group is running. If yes, return true.
203
+ def running?
204
+ @applications.each { |a| return true if a.running? }
205
+ return false
206
+ end
207
+
208
+ end
194
209
  end
@@ -1,9 +1,7 @@
1
1
  require 'daemons/etc_extension'
2
2
 
3
3
  class CurrentProcess
4
- def self.change_privilege(user, group=user)
5
- puts "Changing process privilege to #{user}:#{group}"
6
-
4
+ def self.change_privilege(user, group = user)
7
5
  uid, gid = Process.euid, Process.egid
8
6
  target_uid = Etc.getpwnam(user).uid
9
7
  target_gid = Etc.getgrnam(group).gid
@@ -16,4 +14,4 @@ class CurrentProcess
16
14
  rescue Errno::EPERM => e
17
15
  raise "Couldn't change user and group to #{user}:#{group}: #{e}"
18
16
  end
19
- end
17
+ end
@@ -1,56 +1,78 @@
1
-
2
1
  module Daemons
3
-
4
2
  class Optparse
5
-
6
3
  attr_reader :usage
7
4
 
8
5
  def initialize(controller)
9
6
  @controller = controller
10
7
  @options = {}
11
-
8
+
12
9
  @opts = OptionParser.new do |opts|
13
- opts.banner = ""
14
-
15
- # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
16
- # @options[:verbose] = v
17
- # end
18
-
19
- opts.on("-t", "--ontop", "Stay on top (does not daemonize)") do |t|
10
+ opts.banner = ''
11
+
12
+ opts.on('-t', '--ontop', 'Stay on top (does not daemonize)') do |t|
20
13
  @options[:ontop] = t
21
14
  end
22
-
23
- opts.on("-f", "--force", "Force operation") do |t|
15
+
16
+ opts.on('-s', '--shush', 'Silent mode (no output to the terminal)') do |t|
17
+ @options[:shush] = t
18
+ end
19
+
20
+ opts.on('-f', '--force', 'Force operation') do |t|
24
21
  @options[:force] = t
25
22
  end
26
-
27
- opts.on("-n", "--no_wait", "Do not wait for processes to stop") do |t|
23
+
24
+ opts.on('-n', '--no_wait', 'Do not wait for processes to stop') do |t|
28
25
  @options[:no_wait] = t
29
26
  end
30
-
31
- #opts.separator ""
32
- #opts.separator "Specific options:"
33
27
 
34
-
35
- opts.separator ""
36
- opts.separator "Common options:"
28
+ opts.on('-w', '--force_kill_waittime SECONDS', Integer, 'Maximum time to wait for processes to stop before force-killing') do |t|
29
+ @options[:force_kill_waittime] = t
30
+ end
31
+ opts.on('--signals_and_waits STRING', String, 'which signal to use to stop and how long to wait e.g. TERM:20|KILL:20') do |t|
32
+ @options[:signals_and_waits] = t
33
+ end
34
+
35
+ opts.on('--pid_delimiter STRING', 'Text used to separate process number in full process name and pid-file name') do |value|
36
+ @options[:pid_delimiter] = value
37
+ end
38
+
39
+ opts.separator ''
40
+ opts.separator 'Common options:'
41
+
42
+ opts.on('-l', '--log_output', 'Enable input/output stream redirection') do |value|
43
+ @options[:log_output] = value
44
+ end
45
+
46
+ opts.on('--logfilename FILE', String, 'Custom log file name for exceptions') do |value|
47
+ @options[:logfilename] = value
48
+ end
49
+
50
+ opts.on('--output_logfilename FILE', String, 'Custom file name for input/output stream redirection log') do |value|
51
+ @options[:output_logfilename] = value
52
+ end
53
+
54
+ opts.on('--log_dir DIR', String, 'Directory for log files') do |value|
55
+ @options[:log_dir] = value
56
+ end
57
+
58
+ opts.on('--syslog', 'Enable output redirction into SYSLOG instead of a file') do |value|
59
+ @options[:log_output_syslog] = value
60
+ end
37
61
 
38
62
  # No argument, shows at tail. This will print an options summary
39
- opts.on_tail("-h", "--help", "Show this message") do
40
- #puts opts
41
- #@usage =
42
- controller.print_usage()
43
-
63
+ opts.on_tail('-h', '--help', 'Show this message') do
64
+ controller.print_usage
65
+
44
66
  exit
45
67
  end
46
68
 
47
69
  # Switch to print the version.
48
- opts.on_tail("--version", "Show version") do
70
+ opts.on_tail('--version', 'Show version') do
49
71
  puts "daemons version #{Daemons::VERSION}"
50
72
  exit
51
73
  end
52
- end
53
-
74
+ end
75
+
54
76
  begin
55
77
  @usage = @opts.to_s
56
78
  rescue ::Exception # work around a bug in ruby 1.9
@@ -65,57 +87,48 @@ module Daemons
65
87
  END
66
88
  end
67
89
  end
68
-
69
-
70
- #
90
+
71
91
  # Return a hash describing the options.
72
92
  #
73
93
  def parse(args)
74
94
  # The options specified on the command line will be collected in *options*.
75
95
  # We set default values here.
76
- #options = {}
77
-
78
-
79
- ##pp args
96
+
80
97
  @opts.parse(args)
81
-
82
- return @options
83
- end
84
98
 
99
+ @options
100
+ end
85
101
  end
86
-
87
-
102
+
88
103
  class Controller
89
-
90
104
  def print_usage
91
- puts "Usage: #{@app_name} <command> <options> -- <application options>"
92
- puts
93
- puts "* where <command> is one of:"
94
- puts " start start an instance of the application"
95
- puts " stop stop all instances of the application"
96
- puts " restart stop all instances and restart them afterwards"
97
- puts " reload send a SIGHUP to all instances of the application"
98
- puts " run start the application and stay on top"
99
- puts " zap set the application to a stopped state"
100
- puts " status show status (PID) of application instances"
101
- puts
102
- puts "* and where <options> may contain several of the following:"
103
-
104
- puts @optparse.usage
105
+ puts <<-USAGE.gsub(/^ {6}/, '')
106
+ Usage: #{@app_name} <command> <options> -- <application options>
107
+
108
+ * where <command> is one of:
109
+ start start an instance of the application
110
+ stop stop all instances of the application
111
+ restart stop all instances and restart them afterwards
112
+ reload send a SIGHUP to all instances of the application
113
+ run run the application in the foreground (same as start -t)
114
+ zap set the application to a stopped state
115
+ status show status (PID) of application instances
116
+
117
+ * and where <options> may contain several of the following:
118
+ #{@optparse.usage}
119
+ USAGE
105
120
  end
106
-
121
+
107
122
  def catch_exceptions(&block)
108
123
  begin
109
124
  block.call
110
125
  rescue CmdException, OptionParser::ParseError => e
111
- puts "ERROR: #{e.to_s}"
126
+ puts "ERROR: #{e}"
112
127
  puts
113
- print_usage()
128
+ print_usage
114
129
  rescue RuntimeException => e
115
- puts "ERROR: #{e.to_s}"
130
+ puts "ERROR: #{e}"
116
131
  end
117
132
  end
118
-
119
133
  end
120
-
121
134
  end
@@ -1,70 +1,56 @@
1
1
 
2
2
  module Daemons
3
3
  class Controller
4
-
5
4
  attr_reader :app_name
6
-
5
+
7
6
  attr_reader :group
8
-
7
+
9
8
  attr_reader :options
10
-
11
-
12
- COMMANDS = [
13
- 'start',
14
- 'stop',
15
- 'restart',
16
- 'run',
17
- 'zap',
18
- 'reload',
19
- 'status'
20
- ]
21
-
9
+
10
+ COMMANDS = %w(start stop restart run zap reload status)
11
+
22
12
  def initialize(options = {}, argv = [])
23
13
  @options = options
24
14
  @argv = argv
25
-
15
+
26
16
  # Allow an app_name to be specified. If not specified use the
27
17
  # basename of the script.
28
18
  @app_name = options[:app_name]
29
-
19
+
30
20
  if options[:script]
31
21
  @script = File.expand_path(options[:script])
32
-
22
+
33
23
  @app_name ||= File.split(@script)[1]
34
24
  end
35
-
25
+
36
26
  @app_name ||= 'unknown_application'
37
-
27
+
38
28
  @command, @controller_part, @app_part = Controller.split_argv(argv)
39
-
40
- #@options[:dir_mode] ||= :script
41
-
29
+
30
+ # @options[:dir_mode] ||= :script
31
+
42
32
  @optparse = Optparse.new(self)
43
33
  end
44
-
45
-
34
+
46
35
  # This function is used to do a final update of the options passed to the application
47
36
  # before they are really used.
48
37
  #
49
38
  # Note that this function should only update <tt>@options</tt> and no other variables.
50
39
  #
51
40
  def setup_options
52
- #@options[:ontop] ||= true
53
41
  end
54
-
42
+
55
43
  def run
56
- @options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
57
-
58
- setup_options()
59
-
60
- #pp @options
44
+ @options.update @optparse.parse(@controller_part).delete_if { |k, v| !v }
45
+
46
+ setup_options
61
47
 
62
48
  @group = ApplicationGroup.new(@app_name, @options)
63
49
  @group.controller_argv = @controller_part
64
50
  @group.app_argv = @app_part
65
-
51
+
66
52
  @group.setup
67
-
53
+
68
54
  case @command
69
55
  when 'start'
70
56
  @group.new_application.start
@@ -75,11 +61,11 @@ module Daemons
75
61
  @group.stop_all(@options[:no_wait])
76
62
  when 'restart'
77
63
  unless @group.applications.empty?
78
- @group.stop_all
64
+ @group.stop_all(@options[:no_wait])
79
65
  sleep(1)
80
66
  @group.start_all
81
67
  else
82
- puts "Warning: no instances running. Starting..."
68
+ $stderr.puts "#{@group.app_name}: warning: no instances running. Starting..."
83
69
  @group.new_application.start
84
70
  end
85
71
  when 'reload'
@@ -89,21 +75,18 @@ module Daemons
89
75
  when 'status'
90
76
  unless @group.applications.empty?
91
77
  @group.show_status
78
+ exit 3 if not @group.running? # exit with status 3 to indicate that no apps are running
92
79
  else
93
- puts "#{@group.app_name}: no instances running"
80
+ $stderr.puts "#{@group.app_name}: no instances running"
81
+ exit 3 # exit with status 3 to indicate that no apps are running
94
82
  end
95
83
  when nil
96
- raise CmdException.new('no command given')
97
- #puts "ERROR: No command given"; puts
98
-
99
- #print_usage()
100
- #raise('usage function not implemented')
84
+ fail CmdException.new('no command given')
101
85
  else
102
- raise Error.new("command '#{@command}' not implemented")
86
+ fail Error.new("command '#{@command}' not implemented")
103
87
  end
104
88
  end
105
-
106
-
89
+
107
90
  # Split an _argv_ array.
108
91
  # +argv+ is assumed to be in the following format:
109
92
  # ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
@@ -113,28 +96,27 @@ module Daemons
113
96
  # *Returns*: the command as a string, the controller options as an array, the appliation options
114
97
  # as an array
115
98
  #
116
- def Controller.split_argv(argv)
99
+ def self.split_argv(argv)
117
100
  argv = argv.dup
118
-
101
+
119
102
  command = nil
120
103
  controller_part = []
121
104
  app_part = []
122
-
105
+
123
106
  if COMMANDS.include? argv[0]
124
107
  command = argv.shift
125
108
  end
126
-
109
+
127
110
  if i = argv.index('--')
128
111
  # Handle the case where no controller options are given, just
129
112
  # options after "--" as well (i == 0)
130
- controller_part = (i == 0 ? [] : argv[0..i-1])
131
- app_part = argv[i+1..-1]
113
+ controller_part = (i == 0 ? [] : argv[0..i - 1])
114
+ app_part = argv[i + 1..-1]
132
115
  else
133
116
  controller_part = argv[0..-1]
134
117
  end
135
-
136
- return command, controller_part, app_part
118
+
119
+ [command, controller_part, app_part]
137
120
  end
138
121
  end
139
-
140
122
  end