blue-daemons 1.1.11
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +22 -0
- data/README-mlanett.rdoc +8 -0
- data/README.rdoc +214 -0
- data/Rakefile +32 -0
- data/Releases +201 -0
- data/TODO +2 -0
- data/daemons.gemspec +27 -0
- data/examples/call/call.rb +57 -0
- data/examples/call/call.rb.log +1 -0
- data/examples/call/call_monitor.rb +55 -0
- data/examples/daemonize/daemonize.rb +27 -0
- data/examples/run/ctrl_crash.rb +17 -0
- data/examples/run/ctrl_exec.rb +16 -0
- data/examples/run/ctrl_exit.rb +15 -0
- data/examples/run/ctrl_hanging.rb +19 -0
- data/examples/run/ctrl_keep_pid_files.rb +17 -0
- data/examples/run/ctrl_monitor.rb +16 -0
- data/examples/run/ctrl_monitor_multiple.rb +18 -0
- data/examples/run/ctrl_multiple.rb +16 -0
- data/examples/run/ctrl_normal.rb +11 -0
- data/examples/run/ctrl_ontop.rb +16 -0
- data/examples/run/ctrl_optionparser.rb +43 -0
- data/examples/run/ctrl_proc.rb +25 -0
- data/examples/run/ctrl_proc.rb.output +121 -0
- data/examples/run/ctrl_proc_multiple.rb +22 -0
- data/examples/run/ctrl_proc_multiple.rb.output +2 -0
- data/examples/run/ctrl_proc_rand.rb +23 -0
- data/examples/run/ctrl_proc_simple.rb +17 -0
- data/examples/run/ctrl_slowstop.rb +16 -0
- data/examples/run/myserver.rb +12 -0
- data/examples/run/myserver_crashing.rb +14 -0
- data/examples/run/myserver_exiting.rb +8 -0
- data/examples/run/myserver_hanging.rb +21 -0
- data/examples/run/myserver_slowstop.rb +21 -0
- data/lib/daemons.rb +312 -0
- data/lib/daemons/application.rb +481 -0
- data/lib/daemons/application_group.rb +200 -0
- data/lib/daemons/change_privilege.rb +19 -0
- data/lib/daemons/cmdline.rb +121 -0
- data/lib/daemons/controller.rb +140 -0
- data/lib/daemons/daemonize.rb +182 -0
- data/lib/daemons/etc_extension.rb +12 -0
- data/lib/daemons/exceptions.rb +31 -0
- data/lib/daemons/monitor.rb +144 -0
- data/lib/daemons/pid.rb +114 -0
- data/lib/daemons/pidfile.rb +118 -0
- data/lib/daemons/pidmem.rb +19 -0
- data/lib/daemons/version.rb +3 -0
- data/setup.rb +1360 -0
- data/spec/pidfile_spec.rb +12 -0
- data/spec/spec_helper.rb +1 -0
- metadata +146 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
module Daemonize
|
2
|
+
|
3
|
+
# Try to fork if at all possible retrying every 5 sec if the
|
4
|
+
# maximum process limit for the system has been reached
|
5
|
+
def safefork
|
6
|
+
tryagain = true
|
7
|
+
|
8
|
+
while tryagain
|
9
|
+
tryagain = false
|
10
|
+
begin
|
11
|
+
if pid = fork
|
12
|
+
return pid
|
13
|
+
end
|
14
|
+
rescue Errno::EWOULDBLOCK
|
15
|
+
sleep 5
|
16
|
+
tryagain = true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
module_function :safefork
|
21
|
+
|
22
|
+
|
23
|
+
# Simulate the daemonization process (:ontop mode)
|
24
|
+
# NOTE: STDOUT and STDERR will not be redirected to the logfile,
|
25
|
+
# because in :ontop mode, we normally want to see the output
|
26
|
+
def simulate(logfile_name = nil, app_name = nil)
|
27
|
+
$0 = app_name if app_name
|
28
|
+
|
29
|
+
# Release old working directory
|
30
|
+
Dir.chdir "/"
|
31
|
+
|
32
|
+
# Release old working directory
|
33
|
+
Dir.chdir "/"
|
34
|
+
|
35
|
+
close_io()
|
36
|
+
|
37
|
+
# Free STDIN and point it to somewhere sensible
|
38
|
+
begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
|
39
|
+
|
40
|
+
# Split rand streams between spawning and daemonized process
|
41
|
+
srand
|
42
|
+
end
|
43
|
+
module_function :simulate
|
44
|
+
|
45
|
+
|
46
|
+
# Call a given block as a daemon
|
47
|
+
def call_as_daemon(block, logfile_name = nil, app_name = nil)
|
48
|
+
# we use a pipe to return the PID of the daemon
|
49
|
+
rd, wr = IO.pipe
|
50
|
+
|
51
|
+
if tmppid = safefork
|
52
|
+
# in the parent
|
53
|
+
|
54
|
+
wr.close
|
55
|
+
pid = rd.read.to_i
|
56
|
+
rd.close
|
57
|
+
|
58
|
+
Process.waitpid(tmppid)
|
59
|
+
|
60
|
+
return pid
|
61
|
+
else
|
62
|
+
# in the child
|
63
|
+
|
64
|
+
rd.close
|
65
|
+
|
66
|
+
# Detach from the controlling terminal
|
67
|
+
unless sess_id = Process.setsid
|
68
|
+
raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Prevent the possibility of acquiring a controlling terminal
|
72
|
+
trap 'SIGHUP', 'IGNORE'
|
73
|
+
if pid = safefork
|
74
|
+
wr.close
|
75
|
+
::Process::exit!(true)
|
76
|
+
end
|
77
|
+
|
78
|
+
wr.write Process.pid
|
79
|
+
wr.close
|
80
|
+
|
81
|
+
$0 = app_name if app_name
|
82
|
+
|
83
|
+
# Release old working directory
|
84
|
+
Dir.chdir "/"
|
85
|
+
|
86
|
+
close_io()
|
87
|
+
|
88
|
+
redirect_io(logfile_name)
|
89
|
+
|
90
|
+
# Split rand streams between spawning and daemonized process
|
91
|
+
srand
|
92
|
+
|
93
|
+
block.call
|
94
|
+
|
95
|
+
::Process::exit!(true)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
module_function :call_as_daemon
|
99
|
+
|
100
|
+
|
101
|
+
# Transform the current process into a daemon
|
102
|
+
def daemonize(logfile_name = nil, app_name = nil)
|
103
|
+
# Fork and exit from the parent
|
104
|
+
safefork and exit
|
105
|
+
|
106
|
+
# Detach from the controlling terminal
|
107
|
+
unless sess_id = Process.setsid
|
108
|
+
raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
|
109
|
+
end
|
110
|
+
|
111
|
+
# Prevent the possibility of acquiring a controlling terminal
|
112
|
+
trap 'SIGHUP', 'IGNORE'
|
113
|
+
if pid = safefork
|
114
|
+
::Process::exit!(true)
|
115
|
+
end
|
116
|
+
|
117
|
+
$0 = app_name if app_name
|
118
|
+
|
119
|
+
# Release old working directory
|
120
|
+
Dir.chdir "/"
|
121
|
+
|
122
|
+
close_io()
|
123
|
+
|
124
|
+
redirect_io(logfile_name)
|
125
|
+
|
126
|
+
# Split rand streams between spawning and daemonized process
|
127
|
+
srand
|
128
|
+
|
129
|
+
return sess_id
|
130
|
+
end
|
131
|
+
module_function :daemonize
|
132
|
+
|
133
|
+
|
134
|
+
def close_io()
|
135
|
+
# Make sure all input/output streams are closed
|
136
|
+
# Part I: close all IO objects (except for STDIN/STDOUT/STDERR)
|
137
|
+
ObjectSpace.each_object(IO) do |io|
|
138
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
139
|
+
begin
|
140
|
+
unless io.closed?
|
141
|
+
io.close
|
142
|
+
end
|
143
|
+
rescue ::Exception
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Make sure all input/output streams are closed
|
149
|
+
# Part II: close all file decriptors (except for STDIN/STDOUT/STDERR)
|
150
|
+
ios = Array.new(8192) {|i| IO.for_fd(i) rescue nil}.compact
|
151
|
+
ios.each do |io|
|
152
|
+
next if io.fileno < 3
|
153
|
+
io.close
|
154
|
+
end
|
155
|
+
end
|
156
|
+
module_function :close_io
|
157
|
+
|
158
|
+
|
159
|
+
# Free STDIN/STDOUT/STDERR file descriptors and
|
160
|
+
# point them somewhere sensible
|
161
|
+
def redirect_io(logfile_name)
|
162
|
+
begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
|
163
|
+
|
164
|
+
if logfile_name
|
165
|
+
begin
|
166
|
+
STDOUT.reopen logfile_name, "a"
|
167
|
+
File.chmod(0644, logfile_name)
|
168
|
+
STDOUT.sync = true
|
169
|
+
rescue ::Exception
|
170
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
171
|
+
end
|
172
|
+
else
|
173
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
174
|
+
end
|
175
|
+
|
176
|
+
begin; STDERR.reopen STDOUT; rescue ::Exception; end
|
177
|
+
STDERR.sync = true
|
178
|
+
end
|
179
|
+
module_function :redirect_io
|
180
|
+
|
181
|
+
|
182
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Daemons
|
3
|
+
|
4
|
+
class Exception < ::RuntimeError
|
5
|
+
end
|
6
|
+
|
7
|
+
class RuntimeException < Exception
|
8
|
+
end
|
9
|
+
|
10
|
+
class CmdException < Exception
|
11
|
+
end
|
12
|
+
|
13
|
+
class Error < Exception
|
14
|
+
end
|
15
|
+
|
16
|
+
class SystemError < Error
|
17
|
+
|
18
|
+
attr_reader :system_error
|
19
|
+
|
20
|
+
def initialize(msg, system_error)
|
21
|
+
super(msg)
|
22
|
+
|
23
|
+
@system_error = system_error
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
class TimeoutError < Error
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'daemons/exceptions'
|
2
|
+
|
3
|
+
|
4
|
+
module Daemons
|
5
|
+
|
6
|
+
require 'daemons/daemonize'
|
7
|
+
|
8
|
+
class Monitor
|
9
|
+
|
10
|
+
def self.find(dir, app_name)
|
11
|
+
pid = PidFile.find_files(dir, app_name, false)[0]
|
12
|
+
|
13
|
+
if pid
|
14
|
+
pid = PidFile.existing(pid)
|
15
|
+
|
16
|
+
unless PidFile.running?(pid.pid)
|
17
|
+
begin; pid.cleanup; rescue ::Exception; end
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
monitor = self.allocate
|
22
|
+
|
23
|
+
monitor.instance_variable_set(:@pid, pid)
|
24
|
+
|
25
|
+
return monitor
|
26
|
+
end
|
27
|
+
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def initialize(an_app)
|
33
|
+
@app = an_app
|
34
|
+
@app_name = an_app.group.app_name + '_monitor'
|
35
|
+
|
36
|
+
if an_app.pidfile_dir
|
37
|
+
@pid = PidFile.new(an_app.pidfile_dir, @app_name, false)
|
38
|
+
else
|
39
|
+
@pid = PidMem.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def watch(application_group)
|
44
|
+
sleep(5)
|
45
|
+
|
46
|
+
loop do
|
47
|
+
application_group.applications.each {|a|
|
48
|
+
unless a.running?
|
49
|
+
a.zap!
|
50
|
+
|
51
|
+
sleep(1)
|
52
|
+
|
53
|
+
Process.detach(fork { a.start(restart=true) })
|
54
|
+
|
55
|
+
sleep(5)
|
56
|
+
|
57
|
+
#application_group.setup
|
58
|
+
end
|
59
|
+
}
|
60
|
+
|
61
|
+
#sleep(5)
|
62
|
+
#application_group.setup
|
63
|
+
#sleep(30)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
private :watch
|
67
|
+
|
68
|
+
|
69
|
+
def start_with_pidfile(application_group)
|
70
|
+
fork do
|
71
|
+
Daemonize.daemonize(nil, @app_name)
|
72
|
+
|
73
|
+
begin
|
74
|
+
@pid.pid = Process.pid
|
75
|
+
|
76
|
+
# at_exit {
|
77
|
+
# begin; @pid.cleanup; rescue ::Exception; end
|
78
|
+
# }
|
79
|
+
|
80
|
+
# This part is needed to remove the pid-file if the application is killed by
|
81
|
+
# daemons or manually by the user.
|
82
|
+
# Note that the applications is not supposed to overwrite the signal handler for
|
83
|
+
# 'TERM'.
|
84
|
+
#
|
85
|
+
# trap('TERM') {
|
86
|
+
# begin; @pid.cleanup; rescue ::Exception; end
|
87
|
+
# exit
|
88
|
+
# }
|
89
|
+
|
90
|
+
watch(application_group)
|
91
|
+
rescue ::Exception => e
|
92
|
+
begin
|
93
|
+
File.open(@app.logfile, 'a') {|f|
|
94
|
+
f.puts Time.now
|
95
|
+
f.puts e
|
96
|
+
f.puts e.backtrace.inspect
|
97
|
+
}
|
98
|
+
ensure
|
99
|
+
begin; @pid.cleanup; rescue ::Exception; end
|
100
|
+
exit!
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
private :start_with_pidfile
|
106
|
+
|
107
|
+
def start_without_pidfile(application_group)
|
108
|
+
Thread.new { watch(application_group) }
|
109
|
+
end
|
110
|
+
private :start_without_pidfile
|
111
|
+
|
112
|
+
|
113
|
+
def start(application_group)
|
114
|
+
return if application_group.applications.empty?
|
115
|
+
|
116
|
+
if @pid.kind_of?(PidFile)
|
117
|
+
start_with_pidfile(application_group)
|
118
|
+
else
|
119
|
+
start_without_pidfile(application_group)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def stop
|
125
|
+
begin
|
126
|
+
pid = @pid.pid
|
127
|
+
Process.kill(Application::SIGNAL, pid)
|
128
|
+
Timeout::timeout(5, TimeoutError) {
|
129
|
+
while Pid.running?(pid)
|
130
|
+
sleep(0.1)
|
131
|
+
end
|
132
|
+
}
|
133
|
+
rescue ::Exception => e
|
134
|
+
puts "#{e} #{pid}"
|
135
|
+
puts "deleting pid-file."
|
136
|
+
end
|
137
|
+
|
138
|
+
# We try to remove the pid-files by ourselves, in case the application
|
139
|
+
# didn't clean it up.
|
140
|
+
begin; @pid.cleanup; rescue ::Exception; end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
data/lib/daemons/pid.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'daemons/exceptions'
|
2
|
+
|
3
|
+
|
4
|
+
module Daemons
|
5
|
+
|
6
|
+
class Pid
|
7
|
+
|
8
|
+
def Pid.running?(pid)
|
9
|
+
# We have seen an odd problem where the pid file exists but is empty
|
10
|
+
# In this case we want to not send a kill but we do want to zap the file
|
11
|
+
# Thus while technically pid==0 => this process => running=true, treat it as running=false
|
12
|
+
return false if ! pid || pid == 0
|
13
|
+
|
14
|
+
# Check if process is in existence
|
15
|
+
# The simplest way to do this is to send signal '0'
|
16
|
+
# (which is a single system call) that doesn't actually
|
17
|
+
# send a signal
|
18
|
+
begin
|
19
|
+
Process.kill(0, pid)
|
20
|
+
return true
|
21
|
+
rescue TimeoutError
|
22
|
+
raise
|
23
|
+
rescue Errno::ESRCH
|
24
|
+
return false
|
25
|
+
rescue ::Exception # for example on EPERM (process exists but does not belong to us)
|
26
|
+
return true
|
27
|
+
#rescue Errno::EPERM
|
28
|
+
# return false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# def Pid.running?(pid, additional = nil)
|
33
|
+
# match_pid = Regexp.new("^\\s*#{pid}\\s")
|
34
|
+
# got_match = false
|
35
|
+
#
|
36
|
+
# #ps_all = IO.popen('ps ax') # the correct syntax is without a dash (-) !
|
37
|
+
# ps_in, ps_out, ps_err = Open3.popen3('ps ax') # the correct syntax is without a dash (-) !
|
38
|
+
#
|
39
|
+
# return true unless ps_out.gets
|
40
|
+
#
|
41
|
+
# begin
|
42
|
+
# ps_out.each { |psline|
|
43
|
+
# next unless psline =~ match_pid
|
44
|
+
# got_match = true
|
45
|
+
# got_match = false if additional and psline !~ /#{additional}/
|
46
|
+
# break
|
47
|
+
# }
|
48
|
+
# ensure
|
49
|
+
# begin; begin; ps_in.close; rescue ::Exception; end; begin; ps_out.close; rescue ::Exception; end; ps_err.close; rescue ::Exception; end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# # an alternative would be to use the code below, but I don't know whether this is portable
|
53
|
+
# # `ps axo pid=`.split.include? pid.to_s
|
54
|
+
#
|
55
|
+
# return got_match
|
56
|
+
# end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
# Returns the directory that should be used to write the pid file to
|
61
|
+
# depending on the given mode.
|
62
|
+
#
|
63
|
+
# Some modes may require an additionaly hint, others may determine
|
64
|
+
# the directory automatically.
|
65
|
+
#
|
66
|
+
# If no valid directory is found, returns nil.
|
67
|
+
#
|
68
|
+
def Pid.dir(dir_mode, dir, script)
|
69
|
+
# nil script parameter is allowed as long as dir_mode is not :script
|
70
|
+
return nil if dir_mode == :script && script.nil?
|
71
|
+
|
72
|
+
case dir_mode
|
73
|
+
when :normal
|
74
|
+
return File.expand_path(dir)
|
75
|
+
when :script
|
76
|
+
return File.expand_path(File.join(File.dirname(script),dir))
|
77
|
+
when :system
|
78
|
+
return '/var/run'
|
79
|
+
else
|
80
|
+
raise Error.new("pid file mode '#{dir_mode}' not implemented")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Initialization method
|
85
|
+
def initialize
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Get method
|
90
|
+
def pid
|
91
|
+
end
|
92
|
+
|
93
|
+
# Set method
|
94
|
+
def pid=(p)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Check whether the process is running
|
98
|
+
def running?
|
99
|
+
return Pid.running?(pid())
|
100
|
+
end
|
101
|
+
|
102
|
+
# Cleanup method
|
103
|
+
def cleanup
|
104
|
+
end
|
105
|
+
|
106
|
+
# Exist? method
|
107
|
+
def exist?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
end
|