wonko9-i_can_daemonize 0.0.0
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/History.txt +4 -0
- data/Manifest.txt +58 -0
- data/README.txt +99 -0
- data/Rakefile +37 -0
- data/lib/i_can_daemonize.rb +482 -0
- data/test/simple_daemon.rb +26 -0
- data/test/test_helper.rb +14 -0
- data/test/test_i_can_daemonize.rb +39 -0
- metadata +64 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
.git/COMMIT_EDITMSG
|
2
|
+
.git/FETCH_HEAD
|
3
|
+
.git/HEAD
|
4
|
+
.git/ORIG_HEAD
|
5
|
+
.git/config
|
6
|
+
.git/description
|
7
|
+
.git/hooks/applypatch-msg
|
8
|
+
.git/hooks/commit-msg
|
9
|
+
.git/hooks/post-commit
|
10
|
+
.git/hooks/post-receive
|
11
|
+
.git/hooks/post-update
|
12
|
+
.git/hooks/pre-applypatch
|
13
|
+
.git/hooks/pre-commit
|
14
|
+
.git/hooks/pre-rebase
|
15
|
+
.git/hooks/prepare-commit-msg
|
16
|
+
.git/hooks/update
|
17
|
+
.git/index
|
18
|
+
.git/info/exclude
|
19
|
+
.git/logs/HEAD
|
20
|
+
.git/logs/refs/heads/master
|
21
|
+
.git/logs/refs/remotes/origin/HEAD
|
22
|
+
.git/logs/refs/remotes/origin/master
|
23
|
+
.git/objects/12/d7cbe8190afdd97a7a0859d9ce822ebc9846cd
|
24
|
+
.git/objects/46/c5d39136616bca92118f3dbf16e474a9df7379
|
25
|
+
.git/objects/4b/0480e9beae3ad3b1b17effeeaa1201560d7d02
|
26
|
+
.git/objects/61/22343c51b970ba436bc47e3cf97f6221492291
|
27
|
+
.git/objects/67/672462a81c734937a6b579751405f168e6fcb0
|
28
|
+
.git/objects/69/69a6a4e8a6d185528b33de649ea3feb1516fb7
|
29
|
+
.git/objects/6a/ddd18336cbaebdff0f727a06305c9232dbb058
|
30
|
+
.git/objects/6c/eeb3df1cb65a06e0b434b7a78551d1d7c1c242
|
31
|
+
.git/objects/73/ae5eefb32e4f335716aa281e9f78c3a61398b1
|
32
|
+
.git/objects/8f/9c4a5a3c9a41935160b96fe005a54b3d3f18b2
|
33
|
+
.git/objects/9c/878acd4b53fb8abaed6ba81ef78e2c3268c9f5
|
34
|
+
.git/objects/a3/5a57b3e8003dcdd039a48441554799b417779d
|
35
|
+
.git/objects/c3/d75d5327524a548ed2cfb997342b2107b83403
|
36
|
+
.git/objects/cb/34e6ff08e94036d69d296284a62d723a2d8a90
|
37
|
+
.git/objects/d4/b772650c4ad1378392aa5f2bb3c773a3818b73
|
38
|
+
.git/objects/f5/9a3860619e3b9d956caa110ff00d0449977fea
|
39
|
+
.git/objects/pack/pack-4efe288a1b0fd1df2942754b498c4480f00a7d52.idx
|
40
|
+
.git/objects/pack/pack-4efe288a1b0fd1df2942754b498c4480f00a7d52.keep
|
41
|
+
.git/objects/pack/pack-4efe288a1b0fd1df2942754b498c4480f00a7d52.pack
|
42
|
+
.git/refs/heads/master
|
43
|
+
.git/refs/remotes/origin/HEAD
|
44
|
+
.git/refs/remotes/origin/master
|
45
|
+
.gitignore
|
46
|
+
History.txt
|
47
|
+
Manifest.txt
|
48
|
+
README.txt
|
49
|
+
Rakefile
|
50
|
+
examples/feature_demo.rb
|
51
|
+
examples/rails_daemon.rb
|
52
|
+
examples/simple_daemon.rb
|
53
|
+
examples/starling_queue_daemon.rb
|
54
|
+
lib/i_can_daemonize.rb
|
55
|
+
lib/i_can_daemonize/version.rb
|
56
|
+
test/simple_daemon.rb
|
57
|
+
test/test_helper.rb
|
58
|
+
test/test_i_can_daemonize.rb
|
data/README.txt
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
= i_can_daemonize
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
ICanDaemonize makes it dead simple to create daemons of your own.
|
6
|
+
|
7
|
+
== REQUIREMENTS:
|
8
|
+
|
9
|
+
* A Computer
|
10
|
+
* Ruby
|
11
|
+
|
12
|
+
== INSTALL:
|
13
|
+
|
14
|
+
* Get ICanDaemonize off github
|
15
|
+
|
16
|
+
== THE BASICS:
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'i_can_daemonize'
|
20
|
+
class MyDaemonClass
|
21
|
+
include ICanDaemonize
|
22
|
+
|
23
|
+
daemonize do
|
24
|
+
# your code here
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Run your daemon
|
30
|
+
|
31
|
+
ruby your_daemon_script start
|
32
|
+
|
33
|
+
ICD will create a log file in log/ called your_daemon_script.log as well as a pid file in log called your_daemon_script.pid
|
34
|
+
|
35
|
+
It will essentially run the block given to daemonize within a loop.
|
36
|
+
|
37
|
+
== USAGE:
|
38
|
+
|
39
|
+
The daemonize method accepts a number of options see ICanDaemonize::ClassMethods daemonize() for options
|
40
|
+
|
41
|
+
There are a number of other blocks you can define in your class as well, including:
|
42
|
+
|
43
|
+
before do/end
|
44
|
+
|
45
|
+
after do/end
|
46
|
+
|
47
|
+
die_if do/end
|
48
|
+
|
49
|
+
exit_if do/end
|
50
|
+
|
51
|
+
See ICanDaemonize docs for more info on these options.
|
52
|
+
|
53
|
+
Your daemon can be called with a number of options
|
54
|
+
|
55
|
+
--loop-every SECONDS How long to sleep between each loop
|
56
|
+
-t, --ontop Stay on top (does not daemonize)
|
57
|
+
--instances=NUM Allow multiple instances to run simultaneously? 0 for infinite. default: 1
|
58
|
+
--log-file=LOGFILE Logfile to log to
|
59
|
+
--pid-file=PIDFILE Directory to put pidfile
|
60
|
+
-h, --help Show this message
|
61
|
+
|
62
|
+
You can add your own command line options by using the 'arg' class macro.
|
63
|
+
See http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html for more info on OptionParser.
|
64
|
+
|
65
|
+
arg '--scott-rocks', 'Does scott rock?') do |value|
|
66
|
+
@scott_rocks = value
|
67
|
+
end
|
68
|
+
|
69
|
+
== BUGS:
|
70
|
+
|
71
|
+
My method names may be too common to be included class methods
|
72
|
+
|
73
|
+
ICanDaemonize attempts to capture all STDOUT or STDERR and prepend that output with a timestamp and PID.
|
74
|
+
I don't like how this was implemented anyway. Feels dirty.
|
75
|
+
|
76
|
+
== LICENSE:
|
77
|
+
|
78
|
+
(The MIT License)
|
79
|
+
|
80
|
+
Copyright (c) 2009 Adam Pisoni, Amos Elliston
|
81
|
+
|
82
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
83
|
+
a copy of this software and associated documentation files (the
|
84
|
+
'Software'), to deal in the Software without restriction, including
|
85
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
86
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
87
|
+
permit persons to whom the Software is furnished to do so, subject to
|
88
|
+
the following conditions:
|
89
|
+
|
90
|
+
The above copyright notice and this permission notice shall be
|
91
|
+
included in all copies or substantial portions of the Software.
|
92
|
+
|
93
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
94
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
95
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
96
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
97
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
98
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
99
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rcov/rcovtask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |s|
|
9
|
+
s.name = "i_can_daemonize"
|
10
|
+
s.summary = "ICanDaemonize makes it dead simple to create daemons of your own"
|
11
|
+
s.email = "wonko9@gmail.com"
|
12
|
+
s.homepage = "http://github.com/wonko9/i_can_daemonize"
|
13
|
+
s.description = "ICanDaemonize makes it dead simple to create daemons of your own"
|
14
|
+
s.authors = ["Adam Pisoni", "Amos Elliston"]
|
15
|
+
s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::TestTask.new
|
22
|
+
|
23
|
+
Rake::RDocTask.new do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'test'
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
27
|
+
rdoc.rdoc_files.include('README*')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
30
|
+
|
31
|
+
Rcov::RcovTask.new do |t|
|
32
|
+
t.libs << 'test'
|
33
|
+
t.test_files = FileList['test/**/*_test.rb']
|
34
|
+
t.verbose = true
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test
|
@@ -0,0 +1,482 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module ICanDaemonize
|
5
|
+
class DieTime < StandardError; end
|
6
|
+
class TimeoutError < StandardError; end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
base.initialize_options
|
11
|
+
end
|
12
|
+
|
13
|
+
class Config
|
14
|
+
METHODS = [:script_path]
|
15
|
+
CONFIG = {}
|
16
|
+
def method_missing(name, *args)
|
17
|
+
name = name.to_s.upcase.to_sym
|
18
|
+
if name.to_s =~ /^(.*)=$/
|
19
|
+
name = $1.to_sym
|
20
|
+
CONFIG[name] = args.first
|
21
|
+
else
|
22
|
+
CONFIG[name]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
|
29
|
+
def initialize_options
|
30
|
+
@@config = Config.new
|
31
|
+
@@config.script_path = File.expand_path(File.dirname($0))
|
32
|
+
$0 = script_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_options
|
36
|
+
opts = OptionParser.new do |opt|
|
37
|
+
opt.banner = "Usage: #{script_name} [options] [start|stop]"
|
38
|
+
|
39
|
+
opt.on_tail('-h', '--help', 'Show this message') do
|
40
|
+
puts opt
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opt.on('--loop-every=SECONDS', 'How long to sleep between each loop') do |value|
|
45
|
+
options[:loop_every] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
opt.on('-t', '--ontop', 'Stay on top (does not daemonize)') do
|
49
|
+
options[:ontop] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
opt.on('--instances=NUM', 'Allow multiple instances to run simultaneously? 0 for infinite. default: 1') do |value|
|
53
|
+
self.instances = value.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
opt.on('--log-file=LOGFILE', 'Logfile to log to') do |value|
|
57
|
+
options[:log_file] = File.expand_path(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
opt.on('--pid-file=PIDFILE', 'Location of pidfile') do |value|
|
61
|
+
options[:pid_file] = File.expand_path(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
opt.on('--no-log-prefix', 'Do not prefix PID and date/time in log file.') do
|
65
|
+
options[:log_prefix] = false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
extra_args.each do |arg|
|
70
|
+
opts.on(*arg.first) do |value|
|
71
|
+
arg.last.call(value) if arg.last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.parse!
|
76
|
+
|
77
|
+
if ARGV.include?('stop')
|
78
|
+
stop_daemons
|
79
|
+
elsif ARGV.include?('restart')
|
80
|
+
restart_daemons
|
81
|
+
elsif ARGV.include?('start') or ontop?
|
82
|
+
self.running = true
|
83
|
+
self.restarted = true if ARGV.include?('HUP')
|
84
|
+
else
|
85
|
+
puts opts.help
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def arg(*args, &block)
|
90
|
+
self.extra_args << [args, block]
|
91
|
+
end
|
92
|
+
|
93
|
+
def extra_args
|
94
|
+
@extra_args ||= []
|
95
|
+
end
|
96
|
+
|
97
|
+
def callbacks
|
98
|
+
@callbacks ||= {}
|
99
|
+
end
|
100
|
+
|
101
|
+
def options
|
102
|
+
@options ||= {}
|
103
|
+
end
|
104
|
+
|
105
|
+
def config
|
106
|
+
yield @@config
|
107
|
+
end
|
108
|
+
|
109
|
+
def before(&block)
|
110
|
+
callbacks[:before] = block
|
111
|
+
end
|
112
|
+
|
113
|
+
def after(&block)
|
114
|
+
callbacks[:after] = block
|
115
|
+
end
|
116
|
+
|
117
|
+
def sig(signal, &block)
|
118
|
+
callbacks["sig_#{signal}".to_sym] = block
|
119
|
+
end
|
120
|
+
|
121
|
+
def die_if(method=nil,&block)
|
122
|
+
options[:die_if] = method || block
|
123
|
+
end
|
124
|
+
|
125
|
+
def exit_if(method=nil,&block)
|
126
|
+
options[:exit_if] = method || block
|
127
|
+
end
|
128
|
+
|
129
|
+
def callback!(callback)
|
130
|
+
callbacks[callback].call if callbacks[callback]
|
131
|
+
end
|
132
|
+
|
133
|
+
# options may include:
|
134
|
+
#
|
135
|
+
# <tt>:loop_every</tt> Fixnum (DEFAULT 0)
|
136
|
+
# How many seconds to sleep between calls to your block
|
137
|
+
#
|
138
|
+
# <tt>:timeout</tt> Fixnum (DEFAULT 0)
|
139
|
+
# Timeout in if block does not execute withing passed number of seconds
|
140
|
+
#
|
141
|
+
# <tt>:die_on_timeout</tt> BOOL (DEFAULT False)
|
142
|
+
# Should the daemon continue running if a block times out, or just run the block again
|
143
|
+
#
|
144
|
+
# <tt>:ontop</tt> BOOL (DEFAULT False)
|
145
|
+
# Do not daemonize. Run in current process
|
146
|
+
#
|
147
|
+
# <tt>:before</tt> BLOCK
|
148
|
+
# Run this block after daemonizing but before begining the daemonize loop.
|
149
|
+
# You can also define the before block by putting a before do/end block in your class.
|
150
|
+
#
|
151
|
+
# <tt>:after</tt> BLOCK
|
152
|
+
# Run this block before program exists.
|
153
|
+
# You can also define the after block by putting an after do/end block in your class.
|
154
|
+
#
|
155
|
+
# <tt>:die_if</tt> BLOCK
|
156
|
+
# Run this check after each iteration of the loop. If the block returns true, throw a DieTime exception and exit
|
157
|
+
# You can also define the after block by putting an die_if do/end block in your class.
|
158
|
+
#
|
159
|
+
# <tt>:exit_if</tt> BLOCK
|
160
|
+
# Run this check after each iteration of the loop. If the block returns true, exit gracefully
|
161
|
+
# You can also define the after block by putting an exit_if do/end block in your class.
|
162
|
+
#
|
163
|
+
# <tt>:log_prefix</tt> BOOL (DEFAULT true)
|
164
|
+
# Prefix log file entries with PID and timestamp
|
165
|
+
def daemonize(opts={}, &block)
|
166
|
+
parse_options
|
167
|
+
return unless ok_to_start?
|
168
|
+
|
169
|
+
options.merge!(opts)
|
170
|
+
puts "Starting #{instances_to_start} #{script_name} #{pluarlize('instance', instances_to_start)}..."
|
171
|
+
puts "Logging to: #{log_file}" unless ontop?
|
172
|
+
|
173
|
+
unless ontop?
|
174
|
+
instances_to_start.times do
|
175
|
+
safefork do
|
176
|
+
open(pid_file, 'a+') {|f| f << Process.pid << "\n"}
|
177
|
+
at_exit { remove_pid! }
|
178
|
+
|
179
|
+
|
180
|
+
trap('TERM') { callback!(:sig_term) ; self.running = false }
|
181
|
+
trap('INT') { callback!(:sig_int) ; Process.kill('TERM', $$) }
|
182
|
+
trap('HUP') { callback!(:sig_hup) ; restart_self }
|
183
|
+
|
184
|
+
sess_id = Process.setsid
|
185
|
+
reopen_filehandes
|
186
|
+
|
187
|
+
begin
|
188
|
+
at_exit { callback!(:after) }
|
189
|
+
callback!(:before)
|
190
|
+
run_block(&block)
|
191
|
+
rescue SystemExit
|
192
|
+
rescue Exception => e
|
193
|
+
$stdout.puts "Something bad happened #{e.inspect} #{e.backtrace.join("\n")}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
else
|
198
|
+
begin
|
199
|
+
callback!(:before)
|
200
|
+
run_block(&block)
|
201
|
+
rescue SystemExit, Interrupt
|
202
|
+
callback!(:after)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def run_block(&block)
|
210
|
+
loop do
|
211
|
+
break unless running?
|
212
|
+
if options[:timeout]
|
213
|
+
begin
|
214
|
+
Timeout::timeout(options[:timeout].to_i) do
|
215
|
+
block.call if block
|
216
|
+
end
|
217
|
+
rescue Timeout::Error => e
|
218
|
+
if options[:die_on_timeout]
|
219
|
+
raise TimeoutError.new("#{self} timed out after #{options[:timeout]} seconds while executing block in loop")
|
220
|
+
else
|
221
|
+
$stderr.puts "#{self} timed out after #{options[:timeout]} seconds while executing block in loop #{e.backtrace.join("\n")}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
else
|
225
|
+
block.call if block
|
226
|
+
|
227
|
+
end
|
228
|
+
if options[:loop_every]
|
229
|
+
sleep options[:loop_every].to_i
|
230
|
+
elsif not block
|
231
|
+
sleep 0.1
|
232
|
+
end
|
233
|
+
break if should_exit?
|
234
|
+
raise DieTime.new('Die if conditions were met!') if should_die?
|
235
|
+
end
|
236
|
+
exit(0)
|
237
|
+
end
|
238
|
+
|
239
|
+
def should_die?
|
240
|
+
die_if = options[:die_if]
|
241
|
+
if die_if
|
242
|
+
if die_if.is_a?(Symbol) or die_if.is_a?(String)
|
243
|
+
self.send(die_if)
|
244
|
+
elsif die_if.is_a?(Proc)
|
245
|
+
die_if.call
|
246
|
+
end
|
247
|
+
else
|
248
|
+
false
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def should_exit?
|
253
|
+
exit_if = options[:exit_if]
|
254
|
+
if exit_if
|
255
|
+
if exit_if.is_a?(Symbol) or exit_if.is_a?(String)
|
256
|
+
self.send(exit_if.to_sym)
|
257
|
+
elsif exit_if.is_a?(Proc)
|
258
|
+
exit_if.call
|
259
|
+
end
|
260
|
+
else
|
261
|
+
false
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def instances_to_start
|
266
|
+
return 1 if restarted?
|
267
|
+
instances - pids.size
|
268
|
+
end
|
269
|
+
|
270
|
+
def ok_to_start?
|
271
|
+
return false unless running?
|
272
|
+
return true if restarted?
|
273
|
+
|
274
|
+
living_pids = []
|
275
|
+
if pids and pids.any?
|
276
|
+
pids.each do |pid|
|
277
|
+
if process_alive?(pid)
|
278
|
+
living_pids << pid
|
279
|
+
else
|
280
|
+
$stderr.puts "Removing stale pid: #{pid}..."
|
281
|
+
pids -= [pid]
|
282
|
+
self.pids = pids
|
283
|
+
end
|
284
|
+
end
|
285
|
+
if instances > 0 and living_pids.size >= instances
|
286
|
+
$stderr.puts "#{script_name} is already running #{living_pids.size} out of #{pluralize('instance', instances)}"
|
287
|
+
return false
|
288
|
+
end
|
289
|
+
end
|
290
|
+
return true
|
291
|
+
end
|
292
|
+
|
293
|
+
# stop the daemon, nicely at first, and then forcefully if necessary
|
294
|
+
def stop_daemons
|
295
|
+
self.running = false
|
296
|
+
puts "Stopping #{instances} #{script_name} #{pluarlize('instance', instances)}..."
|
297
|
+
if pids.empty?
|
298
|
+
$stderr.puts "#{script_name} doesn't appear to be running"
|
299
|
+
exit
|
300
|
+
end
|
301
|
+
pids.each_with_index do |pid, ii|
|
302
|
+
kill_pid(pid)
|
303
|
+
break if ii == (instances - 1)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def restart_daemons
|
308
|
+
pids.each do |pid|
|
309
|
+
kill_pid(pid, 'HUP')
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def kill_pid(pid, signal='TERM')
|
314
|
+
$stdout.puts("Stopping pid #{pid} with #{signal}...")
|
315
|
+
begin
|
316
|
+
Process.kill(signal, pid)
|
317
|
+
if pid_running?(pid, options[:timeout] || 120)
|
318
|
+
$stdout.puts("Using kill -9 #{pid}")
|
319
|
+
Process.kill(9, pid)
|
320
|
+
else
|
321
|
+
$stdout.puts("Process #{pid} stopped")
|
322
|
+
end
|
323
|
+
rescue Errno::ESRCH
|
324
|
+
$stdout.puts("Couldn't #{signal} #{pid} as it wasn't running")
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def pid_running?(pid,time_to_wait=0)
|
329
|
+
times_to_check = 1
|
330
|
+
if time_to_wait > 0.5
|
331
|
+
times_to_check = (time_to_wait / 0.5).to_i
|
332
|
+
end
|
333
|
+
|
334
|
+
begin
|
335
|
+
times_to_check.times do
|
336
|
+
Process.kill(0, pid)
|
337
|
+
sleep 0.5
|
338
|
+
end
|
339
|
+
return true
|
340
|
+
rescue Errno::ESRCH
|
341
|
+
return false
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def restart_self
|
346
|
+
remove_pid!
|
347
|
+
cmd = "#{@@config.script_path}/#{script_name} "
|
348
|
+
cmd << 'HUP ' unless ARGV.include?('HUP')
|
349
|
+
cmd << ARGV.join(' ')
|
350
|
+
puts "Restarting #{cmd} pid: #{$$}..."
|
351
|
+
system(cmd)
|
352
|
+
Process.kill('TERM', $$)
|
353
|
+
end
|
354
|
+
|
355
|
+
def safefork(&block)
|
356
|
+
fork_tries ||= 0
|
357
|
+
fork(&block)
|
358
|
+
rescue Errno::EWOULDBLOCK
|
359
|
+
raise if fork_tries >= 20
|
360
|
+
fork_tries += 1
|
361
|
+
sleep 5
|
362
|
+
retry
|
363
|
+
end
|
364
|
+
|
365
|
+
def process_alive?(process_pid)
|
366
|
+
Process.kill(0, process_pid)
|
367
|
+
return true
|
368
|
+
rescue Errno::ESRCH => e
|
369
|
+
return false
|
370
|
+
end
|
371
|
+
|
372
|
+
LOG_FORMAT = '%-6d %-19s %s'
|
373
|
+
TIME_FORMAT = '%Y/%m/%d %H:%M:%S'
|
374
|
+
def reopen_filehandes
|
375
|
+
STDIN.reopen('/dev/null')
|
376
|
+
STDOUT.reopen(log_file, 'a')
|
377
|
+
STDOUT.sync = true
|
378
|
+
STDERR.reopen(STDOUT)
|
379
|
+
if log_prefix?
|
380
|
+
def STDOUT.write(string)
|
381
|
+
if @no_prefix
|
382
|
+
@no_prefix = false if string[-1, 1] == "\n"
|
383
|
+
else
|
384
|
+
string = LOG_FORMAT % [$$, Time.now.strftime(TIME_FORMAT), string]
|
385
|
+
@no_prefix = true
|
386
|
+
end
|
387
|
+
super(string)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def remove_pid!(pid=Process.pid)
|
393
|
+
pids.delete(pid)
|
394
|
+
self.pids = pids
|
395
|
+
end
|
396
|
+
|
397
|
+
def pids=(pids)
|
398
|
+
if pids.any?
|
399
|
+
open(pid_file, 'w') {|f| f << pids.join("\n") << "\n"}
|
400
|
+
else
|
401
|
+
File.unlink(pid_file) if File.exists?(pid_file)
|
402
|
+
end
|
403
|
+
@pids = pids
|
404
|
+
end
|
405
|
+
|
406
|
+
def pids
|
407
|
+
@pids ||= begin
|
408
|
+
if File.exist?(pid_file)
|
409
|
+
File.readlines(pid_file).collect {|p| p.to_i}
|
410
|
+
else
|
411
|
+
[]
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def instances=(num)
|
417
|
+
@instances = num
|
418
|
+
end
|
419
|
+
|
420
|
+
def instances
|
421
|
+
@instances ||= 1
|
422
|
+
end
|
423
|
+
|
424
|
+
def pluarlize(name, num)
|
425
|
+
num == 1 ? name : "#{name}s"
|
426
|
+
end
|
427
|
+
|
428
|
+
def running?
|
429
|
+
@running || false
|
430
|
+
end
|
431
|
+
|
432
|
+
def running=(bool)
|
433
|
+
@running = bool
|
434
|
+
end
|
435
|
+
|
436
|
+
def restarted?
|
437
|
+
@restarted || false
|
438
|
+
end
|
439
|
+
|
440
|
+
def restarted=(bool)
|
441
|
+
@restarted = bool
|
442
|
+
end
|
443
|
+
|
444
|
+
def ontop?
|
445
|
+
options[:ontop]
|
446
|
+
end
|
447
|
+
|
448
|
+
def log_prefix?
|
449
|
+
options[:log_prefix] || true
|
450
|
+
end
|
451
|
+
|
452
|
+
LOG_PATHS = ['log/', 'logs/', '../log/', '../logs/', '../../log', '../../logs', '.']
|
453
|
+
LOG_PATHS.unshift("#{RAILS_ROOT}/log") if defined?(RAILS_ROOT)
|
454
|
+
def log_dir
|
455
|
+
options[:log_dir] ||= begin
|
456
|
+
LOG_PATHS.detect do |path|
|
457
|
+
File.exists?(File.expand_path(path))
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def log_file
|
463
|
+
options[:log_file] ||= File.expand_path("#{log_dir}/#{script_name}.log")
|
464
|
+
end
|
465
|
+
|
466
|
+
def pid_dir
|
467
|
+
options[:pid_dir] ||= log_dir
|
468
|
+
end
|
469
|
+
|
470
|
+
def pid_file
|
471
|
+
options[:pid_file] ||= File.expand_path("#{pid_dir}/#{script_name}.pid")
|
472
|
+
end
|
473
|
+
|
474
|
+
def script_name
|
475
|
+
@script_name ||= File.basename($0).gsub('.rb', '')
|
476
|
+
end
|
477
|
+
|
478
|
+
def script_name=(script_name)
|
479
|
+
@script_name = script_name
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/i_can_daemonize'
|
3
|
+
|
4
|
+
class SimpleDaemon
|
5
|
+
include ICanDaemonize
|
6
|
+
|
7
|
+
arg '--test=VALUE', 'Test Arg' do |value|
|
8
|
+
@test = value
|
9
|
+
end
|
10
|
+
|
11
|
+
arg '-s', '--short-test=VALUE', 'Test arg with shortname' do |value|
|
12
|
+
@short_test = value
|
13
|
+
end
|
14
|
+
|
15
|
+
counter = 0
|
16
|
+
daemonize do
|
17
|
+
if @options[:loop_every]
|
18
|
+
counter += 1
|
19
|
+
File.open(TEST_FILE, 'w'){|f| f << counter}
|
20
|
+
elsif @test
|
21
|
+
File.open(TEST_FILE, 'w'){|f| f << "#{@test}|#{@short_test}"}
|
22
|
+
else
|
23
|
+
File.open(TEST_FILE, 'w'){|f| f << "#{log_file}|#{pid_file}"}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
TEST_FILE = File.dirname(__FILE__) + '/test.txt' unless defined?(TEST_FILE)
|
5
|
+
|
6
|
+
unless Test::Unit::TestCase.respond_to?(:test)
|
7
|
+
class << Test::Unit::TestCase
|
8
|
+
def test(name, &block)
|
9
|
+
test_name = "test_#{name.gsub(/[\s\W]/,'_')}"
|
10
|
+
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include? test_name
|
11
|
+
define_method test_name, &block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestICanDaemonize < Test::Unit::TestCase
|
4
|
+
DEFAULT_LOG_FILE = File.join(File.dirname(__FILE__), 'test_daemon.rb.log')
|
5
|
+
|
6
|
+
def setup
|
7
|
+
File.delete(TEST_FILE) if File.exist?(TEST_FILE)
|
8
|
+
@daemon = "#{File.dirname(__FILE__)}/simple_daemon.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
File.delete(TEST_FILE) if File.exist?(TEST_FILE)
|
13
|
+
File.delete(DEFAULT_LOG_FILE) if File.exist?(DEFAULT_LOG_FILE)
|
14
|
+
end
|
15
|
+
|
16
|
+
test "passing options" do
|
17
|
+
log_file = File.expand_path(File.join(File.dirname(__FILE__), 'test.log'))
|
18
|
+
pid_file = File.expand_path(File.join(File.dirname(__FILE__), 'test.pid'))
|
19
|
+
`ruby #{@daemon} --log-file #{log_file} --pid-file #{pid_file} start`
|
20
|
+
`ruby #{@daemon} --log-file #{log_file} --pid-file #{pid_file} stop`
|
21
|
+
File.delete(log_file)
|
22
|
+
assert_equal "#{log_file}|#{pid_file}", File.read(TEST_FILE)
|
23
|
+
end
|
24
|
+
|
25
|
+
test "loop every" do
|
26
|
+
`ruby #{@daemon} --loop-every 1 start`
|
27
|
+
sleep 5
|
28
|
+
`ruby #{@daemon} stop`
|
29
|
+
counter = File.read(TEST_FILE).to_i
|
30
|
+
assert counter > 5
|
31
|
+
end
|
32
|
+
|
33
|
+
test "arg class macro" do
|
34
|
+
`ruby #{@daemon} --test test -s short-test start`
|
35
|
+
`ruby #{@daemon} stop`
|
36
|
+
assert_equal "test|short-test", File.read(TEST_FILE)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wonko9-i_can_daemonize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Pisoni
|
8
|
+
- Amos Elliston
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-01-19 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: ICanDaemonize makes it dead simple to create daemons of your own
|
18
|
+
email: wonko9@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- History.txt
|
27
|
+
- Manifest.txt
|
28
|
+
- Rakefile
|
29
|
+
- README.txt
|
30
|
+
- lib/i_can_daemonize
|
31
|
+
- lib/i_can_daemonize/version.rb
|
32
|
+
- lib/i_can_daemonize.rb
|
33
|
+
- test/simple_daemon.rb
|
34
|
+
- test/test_helper.rb
|
35
|
+
- test/test_i_can_daemonize.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/wonko9/i_can_daemonize
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options:
|
40
|
+
- --inline-source
|
41
|
+
- --charset=UTF-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: ICanDaemonize makes it dead simple to create daemons of your own
|
63
|
+
test_files: []
|
64
|
+
|