sa-i_can_daemonize 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-07-09
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
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 = "sa-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/sonian/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
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 8
4
+ :patch: 0
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'i_can_daemonize'
3
+
4
+ class ICanDaemonize::FeatureDemo
5
+ include ICanDaemonize
6
+
7
+ def self.define_args(args)
8
+ # "See the OptionParser docs for more info on how to define your own args.\n http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html\n"
9
+ @options[:nobugs] = true
10
+ args.on("--scott-rocks=TRUE", "Thanks scott") do |t|
11
+ @options[:scott_rocks] = t
12
+ end
13
+ args.on("--nobugs=TRUE", "No bugs flag") do |t|
14
+ @options[:nobugs] = false if t == "1" or t.downcase == "false"
15
+ end
16
+ end
17
+
18
+ before do
19
+ puts "The before block is executed after daemonizing, but before looping over the daemonize block"
20
+ if @options[:nobugs]
21
+ puts "Running with no bugs. Pass nobugs=false to run with bugs."
22
+ else
23
+ puts "There mite be busg"
24
+ end
25
+ end
26
+
27
+ after do
28
+ puts "The after block is executed before the program exits gracefully, but is not run if the program dies."
29
+ end
30
+
31
+ die_if do
32
+ puts "The die_if block is executed after every loop and dies if true is returned."
33
+ false
34
+ end
35
+
36
+ exit_if do
37
+ puts "The exit_if block is executed after every loop and exits gracefully if true is returned."
38
+ false
39
+ end
40
+
41
+ daemonize(:loop_every => 3, :timeout=>2, :die_on_timeout => false) do
42
+ puts "The daemonize block is called in a loop."
43
+ end
44
+
45
+ end
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'i_can_daemonize'
3
+ begin
4
+ require File.dirname(__FILE__) + "/../config/environment"
5
+ rescue LoadError
6
+ puts "\n****** ERROR LOADING RAILS ******\n\trails_daemon.rb should be put in your RAILS_ROOT/script directory so it can find your environment.rb\n\tOr you can change the environment require on line 4.\n*********************************\n\n"
7
+ end
8
+
9
+ class ICanDaemonize::RailsDaemon
10
+ include ICanDaemonize
11
+
12
+ before do
13
+ puts "This daemon has access to your entire rails stack and will log to RAILS_ROOT/log"
14
+ end
15
+
16
+ daemonize(:loop_every => 3, :timeout=>2, :die_on_timeout => false) do
17
+ puts "The daemonize block is called in a loop."
18
+ end
19
+
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'i_can_daemonize'
3
+
4
+ class ICanDaemonize::FeatureDemo
5
+ include ICanDaemonize
6
+
7
+ daemonize(:loop_every => 3) do
8
+ puts "The daemonize block is called in a loop."
9
+ end
10
+
11
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ require 'i_can_daemonize'
4
+ begin
5
+ require 'starling'
6
+ rescue LoadError
7
+ puts "\n****** ERROR LOADING STARLING ******\n\tStarling is not installed. Please run 'sudo gem install starling' before running this script.\n*********************************\n\n"
8
+ end
9
+
10
+ class ICanDaemonize::StarlingDaemon
11
+ include ICanDaemonize
12
+
13
+ if ARGV.include?('start')
14
+ puts <<-DOC
15
+
16
+ This daemon will listen to a starling queue called '#{@queue_name}' and print out whatever is added
17
+ First tail this daemon's log in another window.
18
+ The log is @ #{log_file}
19
+ Run irb at the console and type
20
+ > require 'rubygems'
21
+ > require 'starling'
22
+ > starling = Starling.new('127.0.0.1:22122')
23
+ > starling.set('#{@queue_name}','Hi there!')
24
+ Now watch the log file.
25
+
26
+ DOC
27
+ end
28
+
29
+ before do
30
+ @queue_name = "starlingdeamon"
31
+ @starling = Starling.new("127.0.0.1:22122")
32
+ @fetch_count = 0
33
+ end
34
+
35
+ daemonize(:log_prefix => false) do
36
+ puts "Trying to fetch from the '#{@queue_name}' queue. Dequeued #{@fetch_count} so far"
37
+ pp "GOT: ", @starling.get(@queue_name)
38
+ @fetch_count += 1
39
+ end
40
+
41
+ end
@@ -0,0 +1,515 @@
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(1)
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?('rotate')
82
+ rotate_daemons
83
+ elsif ARGV.include?('start') or ontop?
84
+ self.running = true
85
+ self.restarted = true if ARGV.include?('HUP')
86
+ else
87
+ puts opts.help
88
+ end
89
+ end
90
+
91
+ def arg(*args, &block)
92
+ self.extra_args << [args, block]
93
+ end
94
+
95
+ def extra_args
96
+ @extra_args ||= []
97
+ end
98
+
99
+ def callbacks
100
+ @callbacks ||= {}
101
+ end
102
+
103
+ def options
104
+ @options ||= {}
105
+ end
106
+
107
+ def options=(options)
108
+ @options = options
109
+ end
110
+
111
+ def config
112
+ yield @@config
113
+ end
114
+
115
+ def before(&block)
116
+ callbacks[:before] = block
117
+ end
118
+
119
+ def after(&block)
120
+ callbacks[:after] = block
121
+ end
122
+
123
+ def sig(*signals, &block)
124
+ signals.each do |s|
125
+ callbacks["sig_#{s}".to_sym] = block
126
+ end
127
+ end
128
+
129
+ def die_if(method=nil,&block)
130
+ options[:die_if] = method || block
131
+ end
132
+
133
+ def exit_if(method=nil,&block)
134
+ options[:exit_if] = method || block
135
+ end
136
+
137
+ def callback!(callback)
138
+ callbacks[callback].call if callbacks[callback]
139
+ end
140
+
141
+ # options may include:
142
+ #
143
+ # <tt>:loop_every</tt> Fixnum (DEFAULT 0)
144
+ # How many seconds to sleep between calls to your block
145
+ #
146
+ # <tt>:timeout</tt> Fixnum (DEFAULT 0)
147
+ # Timeout in if block does not execute withing passed number of seconds
148
+ #
149
+ # <tt>:kill_timeout</tt> Fixnum (DEFAULT 120)
150
+ # Wait number of seconds before using kill -9 on daemon
151
+ #
152
+ # <tt>:die_on_timeout</tt> BOOL (DEFAULT False)
153
+ # Should the daemon continue running if a block times out, or just run the block again
154
+ #
155
+ # <tt>:ontop</tt> BOOL (DEFAULT False)
156
+ # Do not daemonize. Run in current process
157
+ #
158
+ # <tt>:before</tt> BLOCK
159
+ # Run this block after daemonizing but before begining the daemonize loop.
160
+ # You can also define the before block by putting a before do/end block in your class.
161
+ #
162
+ # <tt>:after</tt> BLOCK
163
+ # Run this block before program exists.
164
+ # You can also define the after block by putting an after do/end block in your class.
165
+ #
166
+ # <tt>:die_if</tt> BLOCK
167
+ # Run this check after each iteration of the loop. If the block returns true, throw a DieTime exception and exit
168
+ # You can also define the after block by putting an die_if do/end block in your class.
169
+ #
170
+ # <tt>:exit_if</tt> BLOCK
171
+ # Run this check after each iteration of the loop. If the block returns true, exit gracefully
172
+ # You can also define the after block by putting an exit_if do/end block in your class.
173
+ #
174
+ # <tt>:log_prefix</tt> BOOL (DEFAULT true)
175
+ # Prefix log file entries with PID and timestamp
176
+ def daemonize(opts={}, &block)
177
+ self.options = opts
178
+ parse_options
179
+ return unless ok_to_start?
180
+
181
+ puts "Starting #{instances_to_start} #{script_name} #{pluralize('instance', instances_to_start)}..."
182
+ puts "Logging to: #{log_file}" unless ontop?
183
+
184
+ unless ontop?
185
+ instances_to_start.times do
186
+ safefork do
187
+ open(pid_file, 'a+') {|f| f << Process.pid << "\n"}
188
+ at_exit { remove_pid! }
189
+
190
+
191
+ trap('TERM') { callback!(:sig_term) ; self.running = false }
192
+ trap('INT') { callback!(:sig_int) ; Process.kill('TERM', $$) }
193
+ trap('HUP') { callback!(:sig_hup) ; restart_self }
194
+ trap('USR1') { callback!(:sig_usr1) ; reopen_filehandes }
195
+
196
+ sess_id = Process.setsid
197
+ reopen_filehandes
198
+
199
+ begin
200
+ at_exit { callback!(:after) }
201
+ callback!(:before)
202
+ run_block(&block)
203
+ rescue SystemExit
204
+ rescue Exception => e
205
+ $stdout.puts "Something bad happened #{e.inspect} #{e.backtrace.join("\n")}"
206
+ end
207
+ end
208
+ end
209
+ else
210
+ begin
211
+ callback!(:before)
212
+ run_block(&block)
213
+ rescue SystemExit, Interrupt
214
+ callback!(:after)
215
+ end
216
+ end
217
+ end
218
+
219
+ private
220
+
221
+ def run_block(&block)
222
+ loop do
223
+ break unless running?
224
+
225
+ if options[:timeout]
226
+ begin
227
+ Timeout::timeout(options[:timeout].to_i) do
228
+ block.call if block
229
+ end
230
+ rescue Timeout::Error => e
231
+ if options[:die_on_timeout]
232
+ raise TimeoutError.new("#{self} timed out after #{options[:timeout]} seconds while executing block in loop")
233
+ else
234
+ $stderr.puts "#{self} timed out after #{options[:timeout]} seconds while executing block in loop #{e.backtrace.join("\n")}"
235
+ end
236
+ end
237
+ else
238
+ block.call if block
239
+ end
240
+
241
+ if options[:loop_every]
242
+ sleep options[:loop_every].to_i
243
+ elsif not block
244
+ sleep 0.1
245
+ end
246
+
247
+ break if should_exit?
248
+ raise DieTime.new('Die if conditions were met!') if should_die?
249
+ end
250
+ exit(0)
251
+ end
252
+
253
+ def should_die?
254
+ die_if = options[:die_if]
255
+ if die_if
256
+ if die_if.is_a?(Symbol) or die_if.is_a?(String)
257
+ self.send(die_if)
258
+ elsif die_if.is_a?(Proc)
259
+ die_if.call
260
+ end
261
+ else
262
+ false
263
+ end
264
+ end
265
+
266
+ def should_exit?
267
+ exit_if = options[:exit_if]
268
+ if exit_if
269
+ if exit_if.is_a?(Symbol) or exit_if.is_a?(String)
270
+ self.send(exit_if.to_sym)
271
+ elsif exit_if.is_a?(Proc)
272
+ exit_if.call
273
+ end
274
+ else
275
+ false
276
+ end
277
+ end
278
+
279
+ def instances_to_start
280
+ return 1 if restarted?
281
+ instances - pids.size
282
+ end
283
+
284
+ def ok_to_start?
285
+ return false unless running?
286
+ return true if restarted?
287
+
288
+ living_pids = []
289
+ if pids and pids.any?
290
+ pids.each do |pid|
291
+ if process_alive?(pid)
292
+ living_pids << pid
293
+ else
294
+ $stderr.puts "Removing stale pid: #{pid}..."
295
+ pids.delete(pid)
296
+ self.pids = pids
297
+ end
298
+ end
299
+ if instances > 0 and living_pids.size >= instances
300
+ $stderr.puts "#{script_name} is already running #{living_pids.size} out of #{instances} #{pluralize('instance', instances)}"
301
+ return false
302
+ end
303
+ end
304
+ return true
305
+ end
306
+
307
+ # stop the daemon, nicely at first, and then forcefully if necessary
308
+ def stop_daemons
309
+ self.running = false
310
+ pids_to_stop = @instances || pids.size
311
+ puts "Stopping #{pids_to_stop} #{script_name} #{pluralize('instance', pids_to_stop)}..."
312
+ if pids.empty?
313
+ $stderr.puts "#{script_name} doesn't appear to be running"
314
+ exit(1)
315
+ end
316
+ pids.each_with_index do |pid, ii|
317
+ kill_pid(pid)
318
+ break if ii == (pids_to_stop - 1)
319
+ end
320
+ end
321
+
322
+ def rotate_daemons
323
+ if pids.empty?
324
+ $stdout.puts "#{script_name} doesn't appear to be running!"
325
+ exit(1)
326
+ end
327
+
328
+ $stdout.puts "Sending SIGUSR1 to #{pids.join(', ')} ..."
329
+ pids.each do |pid|
330
+ begin
331
+ Process.kill('USR1', pid)
332
+ rescue Errno::ESRCH
333
+ $stdout.puts("Couldn't send USR1 #{pid} as it wasn't running")
334
+ end
335
+ end
336
+ end
337
+
338
+ def restart_daemons
339
+ pids.each do |pid|
340
+ kill_pid(pid, 'HUP')
341
+ end
342
+ end
343
+
344
+ def kill_pid(pid, signal='TERM')
345
+ $stdout.puts("Stopping pid #{pid} with #{signal}...")
346
+ begin
347
+ Process.kill(signal, pid)
348
+ if pid_running?(pid, options[:kill_timeout] || 120)
349
+ $stdout.puts("Using kill -9 #{pid}")
350
+ Process.kill(9, pid)
351
+ else
352
+ $stdout.puts("Process #{pid} stopped")
353
+ end
354
+ rescue Errno::ESRCH
355
+ $stdout.puts("Couldn't #{signal} #{pid} as it wasn't running")
356
+ end
357
+ end
358
+
359
+ def pid_running?(pid,time_to_wait=0)
360
+ times_to_check = 1
361
+ if time_to_wait > 0.5
362
+ times_to_check = (time_to_wait / 0.5).to_i
363
+ end
364
+
365
+ begin
366
+ times_to_check.times do
367
+ Process.kill(0, pid)
368
+ sleep 0.5
369
+ end
370
+ return true
371
+ rescue Errno::ESRCH
372
+ return false
373
+ end
374
+ end
375
+
376
+ def restart_self
377
+ remove_pid!
378
+ cmd = "#{@@config.script_path}/#{script_name} "
379
+ cmd << 'HUP ' unless ARGV.include?('HUP')
380
+ cmd << ARGV.join(' ')
381
+ puts "Restarting #{cmd} pid: #{$$}..."
382
+ system(cmd)
383
+ Process.kill('TERM', $$)
384
+ end
385
+
386
+ def safefork(&block)
387
+ fork_tries ||= 0
388
+ fork(&block)
389
+ rescue Errno::EWOULDBLOCK
390
+ raise if fork_tries >= 20
391
+ fork_tries += 1
392
+ sleep 5
393
+ retry
394
+ end
395
+
396
+ def process_alive?(process_pid)
397
+ Process.kill(0, process_pid)
398
+ return true
399
+ rescue Errno::ESRCH => e
400
+ return false
401
+ end
402
+
403
+ LOG_FORMAT = '%-6d %-19s %s'
404
+ TIME_FORMAT = '%Y/%m/%d %H:%M:%S'
405
+ def reopen_filehandes
406
+ STDIN.reopen('/dev/null')
407
+ STDOUT.reopen(log_file, 'a')
408
+ STDOUT.sync = true
409
+ STDERR.reopen(STDOUT)
410
+
411
+ if log_prefix?
412
+ def STDOUT.write(string)
413
+ if @no_prefix
414
+ @no_prefix = false if string[-1, 1] == "\n"
415
+ else
416
+ string = LOG_FORMAT % [$$, Time.now.strftime(TIME_FORMAT), string]
417
+ @no_prefix = true
418
+ end
419
+ super(string)
420
+ end
421
+ end
422
+ end
423
+
424
+ def remove_pid!(pid=Process.pid)
425
+ pids.delete(pid)
426
+ self.pids = pids
427
+ end
428
+
429
+ def pids=(pids)
430
+ if pids.any?
431
+ open(pid_file, 'w') {|f| f << pids.join("\n") << "\n"}
432
+ else
433
+ File.unlink(pid_file) if File.exists?(pid_file)
434
+ end
435
+ @pids = pids
436
+ end
437
+
438
+ def pids
439
+ @pids ||= begin
440
+ if File.exist?(pid_file)
441
+ File.readlines(pid_file).collect {|p| p.to_i}
442
+ else
443
+ []
444
+ end
445
+ end
446
+ end
447
+
448
+ def instances=(num)
449
+ @instances = num
450
+ end
451
+
452
+ def instances
453
+ @instances || 1
454
+ end
455
+
456
+ def pluralize(name, num)
457
+ num == 1 ? name : "#{name}s"
458
+ end
459
+
460
+ def running?
461
+ @running || false
462
+ end
463
+
464
+ def running=(bool)
465
+ @running = bool
466
+ end
467
+
468
+ def restarted?
469
+ @restarted || false
470
+ end
471
+
472
+ def restarted=(bool)
473
+ @restarted = bool
474
+ end
475
+
476
+ def ontop?
477
+ options[:ontop]
478
+ end
479
+
480
+ def log_prefix?
481
+ options[:log_prefix] || true
482
+ end
483
+
484
+ LOG_PATHS = ['log/', 'logs/', '../log/', '../logs/', '../../log', '../../logs', '.']
485
+ LOG_PATHS.unshift("#{RAILS_ROOT}/log") if defined?(RAILS_ROOT)
486
+ def log_dir
487
+ options[:log_dir] ||= begin
488
+ LOG_PATHS.detect do |path|
489
+ File.exists?(File.expand_path(path))
490
+ end
491
+ end
492
+ end
493
+
494
+ def log_file
495
+ options[:log_file] ||= File.expand_path("#{log_dir}/#{script_name}.log")
496
+ end
497
+
498
+ def pid_dir
499
+ options[:pid_dir] ||= log_dir
500
+ end
501
+
502
+ def pid_file
503
+ options[:pid_file] ||= File.expand_path("#{pid_dir}/#{script_name}.pid")
504
+ end
505
+
506
+ def script_name
507
+ @script_name ||= File.basename($0).gsub('.rb', '')
508
+ end
509
+
510
+ def script_name=(script_name)
511
+ @script_name = script_name
512
+ end
513
+
514
+ end
515
+ end
@@ -0,0 +1,29 @@
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
+ sig(:int, :term) do
16
+ end
17
+
18
+ counter = 0
19
+ daemonize do
20
+ if @options[:loop_every]
21
+ counter += 1
22
+ File.open(TEST_FILE, 'w'){|f| f << counter}
23
+ elsif @test
24
+ File.open(TEST_FILE, 'w'){|f| f << "#{@test}|#{@short_test}"}
25
+ else
26
+ File.open(TEST_FILE, 'w'){|f| f << "#{log_file}|#{pid_file}"}
27
+ end
28
+ end
29
+ end
@@ -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,47 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestICanDaemonize < Test::Unit::TestCase
4
+ DEFAULT_LOG_FILE = File.dirname(__FILE__) + '/simple_daemon.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
+ test "delete stale pid" do
40
+ pidfile = File.dirname(__FILE__) + '/testpids.pid'
41
+ File.open(pidfile, 'w'){|f| f << '999999999'}
42
+ `ruby #{@daemon} start --pid-file=#{pidfile}`
43
+ `ruby #{@daemon} stop --pid-file=#{pidfile}`
44
+ assert !File.exist?(pidfile)
45
+ end
46
+
47
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sa-i_can_daemonize
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Pisoni
8
+ - Amos Elliston
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-11-23 00:00:00 -02: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
+ - README.txt
25
+ files:
26
+ - History.txt
27
+ - Manifest.txt
28
+ - README.txt
29
+ - Rakefile
30
+ - VERSION.yml
31
+ - lib/i_can_daemonize.rb
32
+ - test/simple_daemon.rb
33
+ - test/test_helper.rb
34
+ - test/test_i_can_daemonize.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/sonian/i_can_daemonize
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
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.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: ICanDaemonize makes it dead simple to create daemons of your own
63
+ test_files:
64
+ - test/simple_daemon.rb
65
+ - test/test_helper.rb
66
+ - test/test_i_can_daemonize.rb
67
+ - examples/feature_demo.rb
68
+ - examples/rails_daemon.rb
69
+ - examples/simple_daemon.rb
70
+ - examples/starling_queue_daemon.rb