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,8 +1,6 @@
1
1
  require 'daemons/pid'
2
2
 
3
-
4
3
  module Daemons
5
-
6
4
  # === What is a Pid-File?
7
5
  # A <i>Pid-File</i> is a file containing the <i>process identification number</i>
8
6
  # (pid) that is stored in a well-defined location of the filesystem thus allowing other
@@ -15,14 +13,14 @@ module Daemons
15
13
  # === How does a Pid-File look like?
16
14
  #
17
15
  # Pid-Files generated by Daemons have to following format:
18
- # <scriptname>.rb<number>.pid
19
- # (Note that <tt><number></tt> is omitted if only one instance of the script can
16
+ # <scriptname>_num<number>.pid
17
+ # (Note that <tt>_num<number></tt> is omitted if only one instance of the script can
20
18
  # run at any time)
21
19
  #
22
20
  # Each file just contains one line with the pid as string (for example <tt>6432</tt>).
23
- #
21
+ #
24
22
  # === Where are the Pid-Files stored?
25
- #
23
+ #
26
24
  # Daemons is configurable to store the Pid-Files relative to three different locations:
27
25
  # 1. in a directory relative to the directory where the script (the one that is supposed to run
28
26
  # as a daemon) resides (<tt>:script</tt> option for <tt>:dir_mode</tt>)
@@ -30,87 +28,94 @@ module Daemons
30
28
  # 3. in the preconfigured directory <tt>/var/run</tt> (<tt>:system</tt> option for <tt>:dir_mode</tt>)
31
29
  #
32
30
  class PidFile < Pid
31
+ DEFAULT_PID_DELIMITER = '_num'
32
+ attr_reader :dir, :progname, :multiple, :number, :pid_delimiter
33
33
 
34
- attr_reader :dir, :progname, :multiple, :number
34
+ def self.find_files(dir, progname, delete = false, pid_delimiter = nil)
35
+ files = Dir[File.join(dir, "#{progname}#{pid_delimiter || DEFAULT_PID_DELIMITER}*.pid")]
36
+ files = Dir[File.join(dir, "#{progname}.pid")] if files.size == 0
35
37
 
36
- def PidFile.find_files(dir, progname, delete = false)
37
- files = Dir[File.join(dir, "#{progname}*.pid")]
38
-
39
- files.delete_if {|f| not (File.file?(f) and File.readable?(f))}
40
- if delete
38
+ files.delete_if { |f| not (File.file?(f) and File.readable?(f)) }
39
+ if delete
41
40
  files.delete_if do |f|
42
- pid = File.open(f) {|h| h.read}.to_i
43
- rsl = ! Pid.running?(pid)
41
+ pid = File.open(f) { |h| h.read }.to_i
42
+ rsl = !Pid.running?(pid)
44
43
  if rsl
45
- puts "pid-file for killed process #{pid} found (#{f}), deleting."
46
44
  begin; File.unlink(f); rescue ::Exception; end
45
+ yield(pid, f) if block_given?
47
46
  end
48
47
  rsl
49
48
  end
50
49
  end
51
-
52
- return files
50
+
51
+ files
53
52
  end
54
-
55
- def PidFile.existing(path)
53
+
54
+ def self.existing(path)
56
55
  new_instance = PidFile.allocate
57
-
56
+
58
57
  new_instance.instance_variable_set(:@path, path)
59
-
58
+
60
59
  def new_instance.filename
61
- return @path
60
+ @path
62
61
  end
63
-
64
- return new_instance
62
+
63
+ new_instance
65
64
  end
66
-
67
- def initialize(dir, progname, multiple = false)
65
+
66
+ def initialize(dir, progname, multiple = false, pid_delimiter = nil)
68
67
  @dir = File.expand_path(dir)
69
68
  @progname = progname
70
69
  @multiple = multiple
70
+ @pid_delimiter = pid_delimiter || DEFAULT_PID_DELIMITER
71
71
  @number = nil
72
72
  @number = 0 if multiple
73
-
73
+
74
74
  if multiple
75
- while File.exist?(filename) and @number < 1024
75
+ while File.exist?(filename) && @number < 1024
76
76
  @number += 1
77
77
  end
78
-
79
- if @number == 1024
80
- raise RuntimeException('cannot run more than 1024 instances of the application')
78
+
79
+ if @number >= 1024
80
+ fail RuntimeException('cannot run more than 1024 instances of the application')
81
81
  end
82
82
  end
83
83
  end
84
-
84
+
85
85
  def filename
86
- File.join(@dir, "#{@progname}#{ @number or '' }.pid")
86
+ suffix = "#{pid_delimiter}#{@number}" if @number
87
+ File.join(@dir, "#{@progname}#{suffix}.pid")
87
88
  end
88
-
89
+
89
90
  def exist?
90
91
  File.exist? filename
91
92
  end
92
-
93
+
93
94
  def pid=(p)
94
- File.open(filename, 'w') {|f|
95
+ File.open(filename, 'w') do |f|
95
96
  f.chmod(0644)
96
- f.puts p #Process.pid
97
- }
97
+ f.puts p # Process.pid
98
+ end
98
99
  end
99
100
 
100
101
  def cleanup
101
102
  File.delete(filename) if pid == Process.pid
102
103
  end
103
104
 
105
+ def zap
106
+ File.delete(filename) if exist?
107
+ end
108
+
104
109
  def pid
105
110
  begin
106
- File.open(filename) {|f|
107
- return f.gets.to_i
108
- }
111
+ File.open(filename) do |f|
112
+ p = f.gets.to_i
113
+ return nil if p == 0 # Otherwise an invalid pid file becomes pid 0
114
+ return p
115
+ end
109
116
  rescue ::Exception
110
117
  return nil
111
118
  end
112
119
  end
113
-
114
120
  end
115
-
116
- end
121
+ end
@@ -1,19 +1,15 @@
1
1
  require 'daemons/pid'
2
2
 
3
-
4
3
  module Daemons
5
-
6
4
  class PidMem < Pid
7
5
  attr_accessor :pid
8
-
9
- def PidMem.existing(numeric_pid)
6
+
7
+ def self.existing(numeric_pid)
10
8
  new_instance = PidMem.allocate
11
-
9
+
12
10
  new_instance.instance_variable_set(:@pid, numeric_pid)
13
-
14
- return new_instance
11
+
12
+ new_instance
15
13
  end
16
-
17
14
  end
18
-
19
15
  end
@@ -0,0 +1,54 @@
1
+ module Daemons
2
+ class Reporter
3
+ attr_reader :options
4
+
5
+ def initialize(options)
6
+ @options = options
7
+
8
+ if !options[:shush]
9
+ $stdout.sync = true
10
+ end
11
+ end
12
+
13
+ def output_message(message)
14
+ if !options[:shush]
15
+ puts message
16
+ end
17
+ end
18
+
19
+ def changing_process_privilege(user, group = user)
20
+ output_message "Changing process privilege to #{user}:#{group}"
21
+ end
22
+
23
+ def deleted_found_pidfile(pid, f)
24
+ output_message "pid-file for killed process #{pid} found (#{f}), deleting."
25
+ end
26
+
27
+ def process_started(app_name, pid)
28
+ output_message "#{app_name}: process with pid #{pid} started."
29
+ end
30
+
31
+ def backtrace_not_supported
32
+ output_message 'option :backtrace is not supported with :mode => :exec, ignoring'
33
+ end
34
+
35
+ def stopping_process(app_name, pid, sig, wait)
36
+ output_message "#{app_name}: trying to stop process with pid #{pid}#{' forcefully :(' if sig == 'KILL'} sending #{sig} and waiting #{wait}s ..."
37
+ $stdout.flush
38
+ end
39
+
40
+ def cannot_stop_process(app_name, pid)
41
+ output_message "#{app_name}: unable to forcefully kill process with pid #{pid}."
42
+ $stdout.flush
43
+ end
44
+
45
+ def stopped_process(app_name, pid)
46
+ output_message "#{app_name}: process with pid #{pid} successfully stopped."
47
+ $stdout.flush
48
+ end
49
+
50
+ def status(app_name, running, pid_exists, pid)
51
+ output_message "#{app_name}: #{running ? '' : 'not '}running#{(running and pid_exists) ? ' [pid ' + pid.to_s + ']' : ''}#{(pid_exists and not running) ? ' (but pid-file exists: ' + pid.to_s + ')' : ''}"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,240 @@
1
+ # This is a simple class meant to allow using syslog through an IO-like object. Code
2
+ # borrowed from https://github.com/phemmer/ruby-syslogio
3
+ #
4
+ # The usage is simple:
5
+ #
6
+ # require 'syslogio'
7
+ # $stdout = SyslogIO.new("myapp", :local0, :info, $stdout)
8
+ # $stderr = SyslogIO.new("myapp", :local0, :err, $stderr)
9
+ # $stdout.puts "This is a message"
10
+ # $stderr.puts "This is an error"
11
+ # raise StandardError, 'This will get written through the SyslogIO for $stderr'
12
+
13
+ class Daemons::SyslogIO
14
+ require 'syslog'
15
+
16
+ # Indicates whether synchonous IO is enabled.
17
+ # @return [Boolean]
18
+ attr_reader :sync
19
+
20
+ # @!visibility private
21
+ def self.syslog_constant_sym(option)
22
+ return unless option.is_a?(Symbol) or option.is_a?(String)
23
+ option = option.to_s.upcase
24
+ option = "LOG_#{option}" unless option[0..4] == 'LOG_'
25
+ option = option.to_sym
26
+ option
27
+ end
28
+ # @!visibility private
29
+ def self.syslog_constant(option)
30
+ return unless option = syslog_constant_sym(option)
31
+ return Syslog.constants.include?(option) ? Syslog.const_get(option) : nil
32
+ end
33
+ # @!visibility private
34
+ def self.syslog_facility(option)
35
+ return unless option = syslog_constant_sym(option)
36
+ return Syslog::Facility.constants.include?(option) ? Syslog.const_get(option) : nil
37
+ end
38
+ # @!visibility private
39
+ def self.syslog_level(option)
40
+ return unless option = syslog_constant_sym(option)
41
+ return Syslog::Level.constants.include?(option) ? Syslog.const_get(option) : nil
42
+ end
43
+ # @!visibility private
44
+ def self.syslog_option(option)
45
+ return unless option = syslog_constant_sym(option)
46
+ return Syslog::Option.constants.include?(option) ? Syslog.const_get(option) : nil
47
+ end
48
+
49
+ # Creates a new object.
50
+ # You can have as many SyslogIO objects as you like. However because they all share the same syslog connection, some parameters are shared. The identifier shared among all SyslogIO objects, and is set to the value of the last one created. The Syslog options are merged together as a combination of all objects. The facility and level are distinct between each though.
51
+ # If an IO object is provided as an argument, any text written to the SyslogIO object will also be passed through to that IO object.
52
+ #
53
+ # @param identifier [String] Identifier
54
+ # @param facility [Fixnum<Syslog::Facility>] Syslog facility
55
+ # @param level [Fixnum<Syslog::Level>] Syslog level
56
+ # @param option [Fixnum<Syslog::Options>] Syslog option
57
+ # @param passthrough [IO] IO passthrough
58
+ def initialize(*options)
59
+ options.each do |option|
60
+ if option.is_a?(String)
61
+ @ident = option
62
+ elsif value = self.class.syslog_facility(option)
63
+ @facility = value
64
+ elsif value = self.class.syslog_level(option)
65
+ @level = value
66
+ elsif value = self.class.syslog_option(option)
67
+ @options = 0 if @options.nil?
68
+ @options |= value
69
+ elsif option.is_a?(IO)
70
+ @out = option
71
+ else
72
+ raise ArgumentError, "Unknown argument #{option.inspect}"
73
+ end
74
+ end
75
+
76
+ @options ||= 0
77
+ @ident ||= $0.sub(/.*\//, '')
78
+ @facility ||= Syslog::LOG_USER
79
+ @level ||= Syslog::LOG_INFO
80
+
81
+ if Syslog.opened? then
82
+ options = Syslog.options | @options
83
+ @syslog = Syslog.reopen(@ident, options, @facility)
84
+ else
85
+ @syslog = Syslog.open(@ident, @options, @facility)
86
+ end
87
+
88
+ @subs = []
89
+ @sync = false
90
+ @buffer = ''
91
+
92
+ at_exit { flush }
93
+ end
94
+
95
+ # Add a substitution rule
96
+ #
97
+ # These substitutions will be applied to each line before it is logged. This can be useful if some other gem is generating log content and you want to change the formatting.
98
+ # @param regex [Regex]
99
+ def sub_add(regex, replacement)
100
+ @subs << [regex, replacement]
101
+ end
102
+
103
+ # Enable or disable synchronous IO (buffering).
104
+ #
105
+ # When false (default), output will be line buffered. For syslog this is optimal so the log entries are complete lines.
106
+ def sync=(sync)
107
+ if sync != true and sync != false then
108
+ raise ArgumentError, "sync must be true or false"
109
+ end
110
+ @sync = sync
111
+ if sync == true then
112
+ flush
113
+ end
114
+ end
115
+
116
+ # Write to syslog respecting the behavior of the {#sync} setting.
117
+ def write(text)
118
+ if @sync then
119
+ syswrite(text)
120
+ else
121
+ text.split(/(\n)/).each do |line|
122
+ @buffer = @buffer + line.to_s
123
+ if line == "\n" then
124
+ flush
125
+ end
126
+ end
127
+ end
128
+ end
129
+ alias_method :<<, :write
130
+
131
+ # Write to syslog directly, bypassing buffering if enabled.
132
+ def syswrite(text)
133
+ begin
134
+ @out.syswrite(text) if @out and !@out.closed?
135
+ rescue SystemCallError => e
136
+ end
137
+
138
+ text.split(/\n/).each do |line|
139
+ @subs.each do |sub|
140
+ line.sub!(sub[0], sub[1])
141
+ end
142
+ if line == '' or line.match(/^\s*$/) then
143
+ next
144
+ end
145
+ Syslog.log(@facility | @level, line)
146
+ end
147
+ nil
148
+ end
149
+
150
+ # Immediately flush any buffered data
151
+ def flush
152
+ syswrite(@buffer)
153
+ @buffer = ''
154
+ end
155
+
156
+ # Log at the debug level
157
+ #
158
+ # Shorthand for {#log}(text, Syslog::LOG_DEBUG)
159
+ def debug(text)
160
+ log(text, Syslog::LOG_DEBUG)
161
+ end
162
+
163
+ # Log at the info level
164
+ #
165
+ # Shorthand for {#log}(text, Syslog::LOG_INFO)
166
+ def info(text)
167
+ log(text, Syslog::LOG_INFO)
168
+ end
169
+
170
+ # Log at the notice level
171
+ #
172
+ # Shorthand for {#log}(text, Syslog::LOG_NOTICE)
173
+ def notice(text)
174
+ log(text, Syslog::LOG_NOTICE)
175
+ end
176
+ alias_method :notify, :notice
177
+
178
+ # Log at the warning level
179
+ #
180
+ # Shorthand for {#log}(text, Syslog::LOG_WARNING)
181
+ def warn(text)
182
+ log(text, Syslog::LOG_WARNING)
183
+ end
184
+
185
+ # Log at the error level
186
+ #
187
+ # Shorthand for {#log}(text, Syslog::LOG_ERR)
188
+ def error(text)
189
+ log(text, Syslog::LOG_ERR)
190
+ end
191
+
192
+ # Log at the critical level
193
+ #
194
+ # Shorthand for {#log}(text, Syslog::LOG_CRIT)
195
+ def crit(text)
196
+ log(text, Syslog::LOG_CRIT)
197
+ end
198
+ alias_method :fatal, :crit
199
+
200
+ # Log at the emergency level
201
+ #
202
+ # Shorthand for {#log}(text, Syslog::LOG_EMERG)
203
+ def emerg(text)
204
+ log(text, Syslog::LOG_EMERG)
205
+ end
206
+
207
+ # Log a complete line
208
+ #
209
+ # Similar to {#write} but appends a newline if not present.
210
+ def puts(*texts)
211
+ texts.each do |text|
212
+ write(text.chomp + "\n")
213
+ end
214
+ end
215
+
216
+ # Write a complete line at the specified log level
217
+ #
218
+ # Similar to {#puts} but allows changing the log level for just this one message
219
+ def log(text, level = nil)
220
+ if priority.nil? then
221
+ write(text.chomp + "\n")
222
+ else
223
+ priority_bkup = @priority
224
+ #TODO fix this to be less ugly. Temporarily setting an instance variable is evil
225
+ @priority = priority
226
+ write(text.chomp + "\n")
227
+ @priority = priority_bkup
228
+ end
229
+ end
230
+
231
+ # @!visibility private
232
+ def noop(*args)
233
+ end
234
+ alias_method :reopen, :noop
235
+
236
+ # false
237
+ def isatty
238
+ false
239
+ end
240
+ end
@@ -0,0 +1,3 @@
1
+ module Daemons
2
+ VERSION = '1.4.1'
3
+ end