daemons 1.1.9 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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