sa-i_can_daemonize 0.8.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 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