daemons 1.1.9 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +1 -1
- data/README.md +207 -0
- data/Releases +85 -24
- data/examples/call/call.rb +13 -16
- data/examples/call/call_monitor.rb +13 -17
- data/examples/daemonize/daemonize.rb +4 -8
- data/examples/run/ctrl_crash.rb +0 -1
- data/examples/run/ctrl_custom_logfiles.rb +18 -0
- data/examples/run/ctrl_exec.rb +0 -1
- data/examples/run/ctrl_exit.rb +0 -1
- data/examples/run/ctrl_keep_pid_files.rb +1 -3
- data/examples/run/ctrl_monitor.rb +0 -1
- data/examples/run/ctrl_monitor_multiple.rb +17 -0
- data/examples/run/ctrl_monitor_nocrash.rb +15 -0
- data/examples/run/ctrl_multiple.rb +0 -1
- data/examples/run/ctrl_ontop.rb +0 -1
- data/examples/run/ctrl_optionparser.rb +5 -7
- data/examples/run/ctrl_proc.rb +8 -9
- data/examples/run/ctrl_proc_multiple.rb +4 -6
- data/examples/run/ctrl_proc_rand.rb +2 -4
- data/examples/run/ctrl_proc_simple.rb +0 -1
- data/examples/run/myserver.rb +0 -1
- data/examples/run/myserver_crashing.rb +5 -5
- data/examples/run/myserver_exiting.rb +2 -2
- data/examples/run/myserver_hanging.rb +4 -5
- data/examples/run/myserver_slowstop.rb +5 -6
- data/lib/daemons/application.rb +235 -229
- data/lib/daemons/application_group.rb +115 -100
- data/lib/daemons/change_privilege.rb +2 -4
- data/lib/daemons/cmdline.rb +75 -62
- data/lib/daemons/controller.rb +36 -54
- data/lib/daemons/daemonize.rb +74 -75
- data/lib/daemons/etc_extension.rb +3 -4
- data/lib/daemons/exceptions.rb +11 -13
- data/lib/daemons/monitor.rb +57 -77
- data/lib/daemons/pid.rb +26 -56
- data/lib/daemons/pidfile.rb +49 -44
- data/lib/daemons/pidmem.rb +5 -9
- data/lib/daemons/reporter.rb +54 -0
- data/lib/daemons/syslogio.rb +240 -0
- data/lib/daemons/version.rb +3 -0
- data/lib/daemons.rb +87 -77
- metadata +111 -46
- data/README +0 -214
- data/Rakefile +0 -90
- data/TODO +0 -2
- data/setup.rb +0 -1360
data/lib/daemons/pidfile.rb
CHANGED
@@ -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
|
19
|
-
# (Note that <tt
|
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
|
-
|
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
|
-
|
37
|
-
|
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 = !
|
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
|
-
|
50
|
+
|
51
|
+
files
|
53
52
|
end
|
54
|
-
|
55
|
-
def
|
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
|
-
|
60
|
+
@path
|
62
61
|
end
|
63
|
-
|
64
|
-
|
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)
|
75
|
+
while File.exist?(filename) && @number < 1024
|
76
76
|
@number += 1
|
77
77
|
end
|
78
|
-
|
79
|
-
if @number
|
80
|
-
|
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
|
-
|
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')
|
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)
|
107
|
-
|
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
|
data/lib/daemons/pidmem.rb
CHANGED
@@ -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
|
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
|
-
|
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
|