mikehale-daemons 1.0.12.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.
- data/LICENSE +29 -0
- data/README +232 -0
- data/Rakefile +84 -0
- data/Releases +126 -0
- data/TODO +6 -0
- data/daemons.gemspec +49 -0
- data/lib/daemons.rb +284 -0
- data/lib/daemons/application.rb +387 -0
- data/lib/daemons/application_group.rb +226 -0
- data/lib/daemons/change_privilege.rb +19 -0
- data/lib/daemons/cmdline.rb +117 -0
- data/lib/daemons/controller.rb +134 -0
- data/lib/daemons/daemonize.rb +263 -0
- data/lib/daemons/etc_extension.rb +12 -0
- data/lib/daemons/exceptions.rb +28 -0
- data/lib/daemons/monitor.rb +127 -0
- data/lib/daemons/pid.rb +101 -0
- data/lib/daemons/pidfile.rb +111 -0
- data/lib/daemons/pidmem.rb +10 -0
- metadata +74 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# daemonize.rb is a slightly modified version of daemonize.rb was #
|
4
|
+
# from the Daemonize Library written by Travis Whitton #
|
5
|
+
# for details, read the notice below #
|
6
|
+
###############################################################################
|
7
|
+
#++
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# =Daemonize Library
|
11
|
+
#
|
12
|
+
# February. 4, 2005 Travis Whitton <whitton@atlantic.net>
|
13
|
+
#
|
14
|
+
# Daemonize allows you to easily modify any existing Ruby program to run
|
15
|
+
# as a daemon. See README.rdoc for more details.
|
16
|
+
#
|
17
|
+
# == How to install
|
18
|
+
# 1. su to root
|
19
|
+
# 2. ruby install.rb
|
20
|
+
# build the docs if you want to
|
21
|
+
# 3. rdoc --main README.rdoc daemonize.rb README.rdoc
|
22
|
+
#
|
23
|
+
# == Copying
|
24
|
+
# The Daemonize extension module is copywrited free software by Travis Whitton
|
25
|
+
# <whitton@atlantic.net>. You can redistribute it under the terms specified in
|
26
|
+
# the COPYING file of the Ruby distribution.
|
27
|
+
#
|
28
|
+
# == WARRANTY
|
29
|
+
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
30
|
+
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
31
|
+
# WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
|
32
|
+
# PURPOSE.
|
33
|
+
#
|
34
|
+
#
|
35
|
+
# ----
|
36
|
+
#
|
37
|
+
# == Purpose
|
38
|
+
#
|
39
|
+
# Daemonize is a module derived from Perl's Proc::Daemon module. This module
|
40
|
+
# allows you to easily modify any existing Ruby program to run as a daemon.
|
41
|
+
# A daemon is a process that runs in the background with no controlling terminal.
|
42
|
+
# Generally servers (like FTP and HTTP servers) run as daemon processes.
|
43
|
+
# Note, do not make the mistake that a daemon == server. Converting a program
|
44
|
+
# to a daemon by hand is a relatively simple process; however, this module will
|
45
|
+
# save you the effort of repeatedly looking up the procedure, and it will also
|
46
|
+
# insure that your programs are daemonized in the safest and most corrects
|
47
|
+
# fashion possible.
|
48
|
+
#
|
49
|
+
# == Procedure
|
50
|
+
#
|
51
|
+
# The Daemonize module does the following:
|
52
|
+
#
|
53
|
+
# Forks a child and exits the parent process.
|
54
|
+
#
|
55
|
+
# Becomes a session leader (which detaches the program from
|
56
|
+
# the controlling terminal).
|
57
|
+
#
|
58
|
+
# Forks another child process and exits first child. This prevents
|
59
|
+
# the potential of acquiring a controlling terminal.
|
60
|
+
#
|
61
|
+
# Changes the current working directory to "/".
|
62
|
+
#
|
63
|
+
# Clears the file creation mask.
|
64
|
+
#
|
65
|
+
# Closes file descriptors.
|
66
|
+
#
|
67
|
+
# == Example usage
|
68
|
+
#
|
69
|
+
# Using the Daemonize module is extremely simple:
|
70
|
+
#
|
71
|
+
# require 'daemonize'
|
72
|
+
#
|
73
|
+
# class TestDaemon
|
74
|
+
# include Daemonize
|
75
|
+
#
|
76
|
+
# def initialize
|
77
|
+
# daemonize()
|
78
|
+
# loop do
|
79
|
+
# # do some work here
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# == Credits
|
85
|
+
#
|
86
|
+
# Daemonize was written by Travis Whitton and is based on Perl's
|
87
|
+
# Proc::Daemonize, which was written by Earl Hood. The above documentation
|
88
|
+
# is also partially borrowed from the Proc::Daemonize POD documentation.
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
module Daemonize
|
93
|
+
VERSION = "0.1.1m"
|
94
|
+
|
95
|
+
# Try to fork if at all possible retrying every 5 sec if the
|
96
|
+
# maximum process limit for the system has been reached
|
97
|
+
def safefork
|
98
|
+
tryagain = true
|
99
|
+
|
100
|
+
while tryagain
|
101
|
+
tryagain = false
|
102
|
+
begin
|
103
|
+
if pid = fork
|
104
|
+
return pid
|
105
|
+
end
|
106
|
+
rescue Errno::EWOULDBLOCK
|
107
|
+
sleep 5
|
108
|
+
tryagain = true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
module_function :safefork
|
113
|
+
|
114
|
+
|
115
|
+
def simulate(logfile_name = nil)
|
116
|
+
# NOTE: STDOUT and STDERR will not be redirected to the logfile, because in :ontop mode, we normally want to see the output
|
117
|
+
|
118
|
+
Dir.chdir "/" # Release old working directory
|
119
|
+
File.umask 0000 # Insure sensible umask
|
120
|
+
|
121
|
+
# Make sure all file descriptors are closed
|
122
|
+
ObjectSpace.each_object(IO) do |io|
|
123
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
124
|
+
begin
|
125
|
+
unless io.closed?
|
126
|
+
io.close
|
127
|
+
end
|
128
|
+
rescue ::Exception
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Free file descriptors and
|
134
|
+
# point them somewhere sensible
|
135
|
+
# STDOUT/STDERR should go to a logfile
|
136
|
+
|
137
|
+
begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
|
138
|
+
end
|
139
|
+
module_function :simulate
|
140
|
+
|
141
|
+
|
142
|
+
def call_as_daemon(block, logfile_name = nil, app_name = nil)
|
143
|
+
rd, wr = IO.pipe
|
144
|
+
|
145
|
+
if tmppid = safefork
|
146
|
+
# parent
|
147
|
+
wr.close
|
148
|
+
pid = rd.read.to_i
|
149
|
+
rd.close
|
150
|
+
|
151
|
+
Process.waitpid(tmppid)
|
152
|
+
|
153
|
+
return pid
|
154
|
+
else
|
155
|
+
# child
|
156
|
+
|
157
|
+
rd.close
|
158
|
+
|
159
|
+
# Detach from the controlling terminal
|
160
|
+
unless sess_id = Process.setsid
|
161
|
+
raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
|
162
|
+
end
|
163
|
+
|
164
|
+
# Prevent the possibility of acquiring a controlling terminal
|
165
|
+
#if oldmode.zero?
|
166
|
+
trap 'SIGHUP', 'IGNORE'
|
167
|
+
exit if pid = safefork
|
168
|
+
#end
|
169
|
+
|
170
|
+
wr.write Process.pid
|
171
|
+
wr.close
|
172
|
+
|
173
|
+
$0 = app_name if app_name
|
174
|
+
|
175
|
+
Dir.chdir "/" # Release old working directory
|
176
|
+
File.umask 0000 # Insure sensible umask
|
177
|
+
|
178
|
+
# Make sure all file descriptors are closed
|
179
|
+
ObjectSpace.each_object(IO) do |io|
|
180
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
181
|
+
begin
|
182
|
+
unless io.closed?
|
183
|
+
io.close
|
184
|
+
end
|
185
|
+
rescue ::Exception
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
redirect_io(logfile_name)
|
191
|
+
|
192
|
+
block.call
|
193
|
+
|
194
|
+
exit
|
195
|
+
end
|
196
|
+
end
|
197
|
+
module_function :call_as_daemon
|
198
|
+
|
199
|
+
|
200
|
+
# This method causes the current running process to become a daemon
|
201
|
+
def daemonize(logfile_name = nil, app_name = nil)
|
202
|
+
srand # Split rand streams between spawning and daemonized process
|
203
|
+
safefork and exit # Fork and exit from the parent
|
204
|
+
|
205
|
+
# Detach from the controlling terminal
|
206
|
+
unless sess_id = Process.setsid
|
207
|
+
raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
|
208
|
+
end
|
209
|
+
|
210
|
+
# Prevent the possibility of acquiring a controlling terminal
|
211
|
+
#if oldmode.zero?
|
212
|
+
trap 'SIGHUP', 'IGNORE'
|
213
|
+
exit if pid = safefork
|
214
|
+
#end
|
215
|
+
|
216
|
+
$0 = app_name if app_name
|
217
|
+
|
218
|
+
Dir.chdir "/" # Release old working directory
|
219
|
+
File.umask 0000 # Insure sensible umask
|
220
|
+
|
221
|
+
# Make sure all file descriptors are closed
|
222
|
+
ObjectSpace.each_object(IO) do |io|
|
223
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
224
|
+
begin
|
225
|
+
unless io.closed?
|
226
|
+
io.close
|
227
|
+
end
|
228
|
+
rescue ::Exception
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
redirect_io(logfile_name)
|
234
|
+
|
235
|
+
#return oldmode ? sess_id : 0 # Return value is mostly irrelevant
|
236
|
+
return sess_id
|
237
|
+
end
|
238
|
+
module_function :daemonize
|
239
|
+
|
240
|
+
|
241
|
+
# Free file descriptors and
|
242
|
+
# point them somewhere sensible
|
243
|
+
# STDOUT/STDERR should go to a logfile
|
244
|
+
def redirect_io(logfile_name)
|
245
|
+
begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
|
246
|
+
|
247
|
+
if logfile_name
|
248
|
+
begin
|
249
|
+
STDOUT.reopen logfile_name, "a"
|
250
|
+
STDOUT.sync = true
|
251
|
+
rescue ::Exception
|
252
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
253
|
+
end
|
254
|
+
else
|
255
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
256
|
+
end
|
257
|
+
|
258
|
+
begin; STDERR.reopen STDOUT; rescue ::Exception; end
|
259
|
+
STDERR.sync = true
|
260
|
+
end
|
261
|
+
module_function :redirect_io
|
262
|
+
|
263
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
module Daemons
|
3
|
+
|
4
|
+
require 'daemons/daemonize'
|
5
|
+
|
6
|
+
class Monitor
|
7
|
+
|
8
|
+
def self.find(dir, app_name)
|
9
|
+
pid = PidFile.find_files(dir, app_name, false)[0]
|
10
|
+
|
11
|
+
if pid
|
12
|
+
pid = PidFile.existing(pid)
|
13
|
+
|
14
|
+
unless PidFile.running?(pid.pid)
|
15
|
+
begin; pid.cleanup; rescue ::Exception; end
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
monitor = self.allocate
|
20
|
+
|
21
|
+
monitor.instance_variable_set(:@pid, pid)
|
22
|
+
|
23
|
+
return monitor
|
24
|
+
end
|
25
|
+
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def initialize(an_app)
|
31
|
+
@app = an_app
|
32
|
+
@app_name = an_app.group.app_name + '_monitor'
|
33
|
+
|
34
|
+
if an_app.pidfile_dir
|
35
|
+
@pid = PidFile.new(an_app.pidfile_dir, @app_name, false)
|
36
|
+
else
|
37
|
+
@pid = PidMem.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def watch(applications)
|
42
|
+
sleep(30)
|
43
|
+
|
44
|
+
loop do
|
45
|
+
applications.each {|a|
|
46
|
+
sleep(10)
|
47
|
+
|
48
|
+
unless a.running?
|
49
|
+
a.zap!
|
50
|
+
|
51
|
+
Process.detach(fork { a.start })
|
52
|
+
|
53
|
+
sleep(10)
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
sleep(30)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
private :watch
|
61
|
+
|
62
|
+
|
63
|
+
def start_with_pidfile(applications)
|
64
|
+
fork do
|
65
|
+
Daemonize.daemonize(nil, @app_name)
|
66
|
+
|
67
|
+
begin
|
68
|
+
@pid.pid = Process.pid
|
69
|
+
|
70
|
+
# at_exit {
|
71
|
+
# begin; @pid.cleanup; rescue ::Exception; end
|
72
|
+
# }
|
73
|
+
|
74
|
+
# This part is needed to remove the pid-file if the application is killed by
|
75
|
+
# daemons or manually by the user.
|
76
|
+
# Note that the applications is not supposed to overwrite the signal handler for
|
77
|
+
# 'TERM'.
|
78
|
+
#
|
79
|
+
# trap('TERM') {
|
80
|
+
# begin; @pid.cleanup; rescue ::Exception; end
|
81
|
+
# exit
|
82
|
+
# }
|
83
|
+
|
84
|
+
watch(applications)
|
85
|
+
rescue ::Exception => e
|
86
|
+
begin
|
87
|
+
File.open(@app.logfile, 'a') {|f|
|
88
|
+
f.puts Time.now
|
89
|
+
f.puts e
|
90
|
+
f.puts e.backtrace.inspect
|
91
|
+
}
|
92
|
+
ensure
|
93
|
+
begin; @pid.cleanup; rescue ::Exception; end
|
94
|
+
exit!
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
private :start_with_pidfile
|
100
|
+
|
101
|
+
def start_without_pidfile(applications)
|
102
|
+
Thread.new { watch(applications) }
|
103
|
+
end
|
104
|
+
private :start_without_pidfile
|
105
|
+
|
106
|
+
|
107
|
+
def start(applications)
|
108
|
+
return if applications.empty?
|
109
|
+
|
110
|
+
if @pid.kind_of?(PidFile)
|
111
|
+
start_with_pidfile(applications)
|
112
|
+
else
|
113
|
+
start_without_pidfile(applications)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def stop
|
119
|
+
begin; Process.kill(Application::SIGNAL, @pid.pid); rescue ::Exception; end
|
120
|
+
|
121
|
+
# We try to remove the pid-files by ourselves, in case the application
|
122
|
+
# didn't clean it up.
|
123
|
+
begin; @pid.cleanup; rescue ::Exception; end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
data/lib/daemons/pid.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
|
4
|
+
module Daemons
|
5
|
+
|
6
|
+
class Pid
|
7
|
+
|
8
|
+
def Pid.running?(pid)
|
9
|
+
# Check if process is in existence
|
10
|
+
# The simplest way to do this is to send signal '0'
|
11
|
+
# (which is a single system call) that doesn't actually
|
12
|
+
# send a signal
|
13
|
+
begin
|
14
|
+
Process.kill(0, pid)
|
15
|
+
return true
|
16
|
+
rescue Errno::ESRCH
|
17
|
+
return false
|
18
|
+
rescue ::Exception # for example on EPERM (process exists but does not belong to us)
|
19
|
+
return true
|
20
|
+
#rescue Errno::EPERM
|
21
|
+
# return false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# def Pid.running?(pid, additional = nil)
|
26
|
+
# match_pid = Regexp.new("^\\s*#{pid}\\s")
|
27
|
+
# got_match = false
|
28
|
+
#
|
29
|
+
# #ps_all = IO.popen('ps ax') # the correct syntax is without a dash (-) !
|
30
|
+
# ps_in, ps_out, ps_err = Open3.popen3('ps ax') # the correct syntax is without a dash (-) !
|
31
|
+
#
|
32
|
+
# return true unless ps_out.gets
|
33
|
+
#
|
34
|
+
# begin
|
35
|
+
# ps_out.each { |psline|
|
36
|
+
# next unless psline =~ match_pid
|
37
|
+
# got_match = true
|
38
|
+
# got_match = false if additional and psline !~ /#{additional}/
|
39
|
+
# break
|
40
|
+
# }
|
41
|
+
# ensure
|
42
|
+
# begin; begin; ps_in.close; rescue ::Exception; end; begin; ps_out.close; rescue ::Exception; end; ps_err.close; rescue ::Exception; end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# # an alternative would be to use the code below, but I don't know whether this is portable
|
46
|
+
# # `ps axo pid=`.split.include? pid.to_s
|
47
|
+
#
|
48
|
+
# return got_match
|
49
|
+
# end
|
50
|
+
|
51
|
+
|
52
|
+
# Returns the directory that should be used to write the pid file to
|
53
|
+
# depending on the given mode.
|
54
|
+
#
|
55
|
+
# Some modes may require an additionaly hint, others may determine
|
56
|
+
# the directory automatically.
|
57
|
+
#
|
58
|
+
# If no valid directory is found, returns nil.
|
59
|
+
#
|
60
|
+
def Pid.dir(dir_mode, dir, script)
|
61
|
+
# nil script parameter is allowed as long as dir_mode is not :script
|
62
|
+
return nil if dir_mode == :script && script.nil?
|
63
|
+
|
64
|
+
case dir_mode
|
65
|
+
when :normal
|
66
|
+
return File.expand_path(dir)
|
67
|
+
when :script
|
68
|
+
return File.expand_path(File.join(File.dirname(script),dir))
|
69
|
+
when :system
|
70
|
+
return '/var/run'
|
71
|
+
else
|
72
|
+
raise Error.new("pid file mode '#{dir_mode}' not implemented")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Initialization method
|
77
|
+
def initialize
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Get method
|
82
|
+
def pid
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set method
|
86
|
+
def pid=(p)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Cleanup method
|
90
|
+
def cleanup
|
91
|
+
end
|
92
|
+
|
93
|
+
# Exists? method
|
94
|
+
def exist?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|