demon 0.0.666

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.md +108 -0
  2. data/Rakefile +374 -0
  3. data/demon.gemspec +29 -0
  4. data/lib/demon.rb +786 -0
  5. metadata +48 -0
@@ -0,0 +1,108 @@
1
+ ### NAME
2
+
3
+ demon.rb - the ruby daemon library you've been waiting for
4
+
5
+ ### SYNOPSIS
6
+
7
+ a small, flexible, powerful ruby daemon library
8
+
9
+ ### DESCRIPTION
10
+
11
+ demon.rb aims to make it simple to construct extremely well behaved daemon
12
+ programs in no time.
13
+
14
+ it supports both a DSL and object oriented interface to sound unix daemon
15
+ code. unlike some daemon libraries, it makes zero assumptions about how you
16
+ want to organize your code - although some sane defaults exists to help you
17
+ along if you don't what to think about this at all.
18
+
19
+ ### INSTALL
20
+
21
+ gem 'demon'
22
+
23
+ ### USAGE
24
+
25
+ simple usage is simple...
26
+
27
+ ````ruby
28
+
29
+ #! /usr/bin/env ruby
30
+
31
+ # file: a.rb
32
+
33
+ Demon do
34
+ loop do
35
+ stuff_as_a_daemon
36
+ end
37
+ end
38
+
39
+
40
+ ````
41
+
42
+ ````bash
43
+
44
+ ./a.rb help
45
+
46
+ ````
47
+
48
+ ````yaml
49
+
50
+ ---
51
+ start: start in daemon mode
52
+ run: run in the foreground, but otherwise like a daemon
53
+ stop: stop any currently running daemon
54
+ restart: restart any currently running daemon, or start a new one
55
+ pid: print the pid of the running daemon, iff any
56
+ ping: ensure a daemon is running, start one iff not
57
+ signal: hit the daemon, if any, with SIGUSR2
58
+ tail: tail -F all auxillary files (lock files, logs, etc)
59
+ fuser: report the fuser of any auxillary files (lock files, logs, etc)
60
+ log: display the location of the log file
61
+ root: display the location of the root daemon dir (lock files, logs, etc)
62
+ modes: print all modes, even those without "help"
63
+ help: this message
64
+
65
+
66
+ ````
67
+
68
+ a few things to notice about the above daemon:
69
+
70
+ * daemons are *expected* to run for a long time. demon.rb will run the supplied block over and over (aka. it is an implied loop)
71
+ * if the supplied block blows up the error will be logged, and the block retried. we assume you mean to keep your daemon up.
72
+ * the actual script being run must be know to demon.rb. you can let it know by passing a block, or supplying __FILE__ as the first arugment
73
+
74
+ ````ruby
75
+
76
+ Demon __FILE__ do
77
+ # stuff
78
+ end
79
+
80
+ ````
81
+
82
+
83
+ of course, you can have much more find grained control over your daemons
84
+
85
+
86
+ ````ruby
87
+ class MyProgram
88
+ def run
89
+ loop do
90
+ teh_awesome
91
+ end
92
+ end
93
+
94
+ def daemonize!
95
+ program = self
96
+
97
+ demon.start{ program.run }
98
+ end
99
+
100
+ def pid
101
+ demon.run(:pid)
102
+ end
103
+
104
+ def demon
105
+ @demon ||= Demon.new(__FILE__, :root => '~/.my_progarm/')
106
+ end
107
+ end
108
+ ````
@@ -0,0 +1,374 @@
1
+ This.rubyforge_project = 'codeforpeople'
2
+ This.author = "Ara T. Howard"
3
+ This.email = "ara.t.howard@gmail.com"
4
+ This.homepage = "https://github.com/ahoward/#{ This.lib }"
5
+
6
+ task :license do
7
+ open('LICENSE', 'w'){|fd| fd.puts "same as ruby's"}
8
+ end
9
+
10
+ task :default do
11
+ puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
12
+ end
13
+
14
+ task :test do
15
+ run_tests!
16
+ end
17
+
18
+ namespace :test do
19
+ task(:unit){ run_tests!(:unit) }
20
+ task(:functional){ run_tests!(:functional) }
21
+ task(:integration){ run_tests!(:integration) }
22
+ end
23
+
24
+ def run_tests!(which = nil)
25
+ which ||= '**'
26
+ test_dir = File.join(This.dir, "test")
27
+ test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
28
+ test_rbs = Dir.glob(test_glob).sort
29
+
30
+ div = ('=' * 119)
31
+ line = ('-' * 119)
32
+
33
+ test_rbs.each_with_index do |test_rb, index|
34
+ testno = index + 1
35
+ command = "#{ This.ruby } -w -I ./lib -I ./test/lib #{ test_rb }"
36
+
37
+ puts
38
+ say(div, :color => :cyan, :bold => true)
39
+ say("@#{ testno } => ", :bold => true, :method => :print)
40
+ say(command, :color => :cyan, :bold => true)
41
+ say(line, :color => :cyan, :bold => true)
42
+
43
+ system(command)
44
+
45
+ say(line, :color => :cyan, :bold => true)
46
+
47
+ status = $?.exitstatus
48
+
49
+ if status.zero?
50
+ say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
51
+ say("SUCCESS", :color => :green, :bold => true)
52
+ else
53
+ say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
54
+ say("FAILURE", :color => :red, :bold => true)
55
+ end
56
+ say(line, :color => :cyan, :bold => true)
57
+
58
+ exit(status) unless status.zero?
59
+ end
60
+ end
61
+
62
+
63
+ task :gemspec do
64
+ ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
65
+ ignore_directories = ['pkg']
66
+ ignore_files = ['test/log']
67
+
68
+ shiteless =
69
+ lambda do |list|
70
+ list.delete_if do |entry|
71
+ next unless test(?e, entry)
72
+ extension = File.basename(entry).split(%r/[.]/).last
73
+ ignore_extensions.any?{|ext| ext === extension}
74
+ end
75
+ list.delete_if do |entry|
76
+ next unless test(?d, entry)
77
+ dirname = File.expand_path(entry)
78
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
79
+ end
80
+ list.delete_if do |entry|
81
+ next unless test(?f, entry)
82
+ filename = File.expand_path(entry)
83
+ ignore_files.any?{|file| File.expand_path(file) == filename}
84
+ end
85
+ end
86
+
87
+ lib = This.lib
88
+ object = This.object
89
+ version = This.version
90
+ files = shiteless[Dir::glob("**/**")]
91
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
92
+ #has_rdoc = true #File.exist?('doc')
93
+ test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
94
+ summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
95
+ description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass"
96
+
97
+ if This.extensions.nil?
98
+ This.extensions = []
99
+ extensions = This.extensions
100
+ %w( Makefile configure extconf.rb ).each do |ext|
101
+ extensions << ext if File.exists?(ext)
102
+ end
103
+ end
104
+ extensions = [extensions].flatten.compact
105
+
106
+ template =
107
+ if test(?e, 'gemspec.erb')
108
+ Template{ IO.read('gemspec.erb') }
109
+ else
110
+ Template {
111
+ <<-__
112
+ ## #{ lib }.gemspec
113
+ #
114
+
115
+ Gem::Specification::new do |spec|
116
+ spec.name = #{ lib.inspect }
117
+ spec.version = #{ version.inspect }
118
+ spec.platform = Gem::Platform::RUBY
119
+ spec.summary = #{ lib.inspect }
120
+ spec.description = #{ description.inspect }
121
+
122
+ spec.files =\n#{ files.sort.pretty_inspect }
123
+ spec.executables = #{ executables.inspect }
124
+
125
+ spec.require_path = "lib"
126
+
127
+ spec.test_files = #{ test_files.inspect }
128
+
129
+ ### spec.add_dependency 'lib', '>= version'
130
+ #### spec.add_dependency 'map'
131
+
132
+ spec.extensions.push(*#{ extensions.inspect })
133
+
134
+ spec.rubyforge_project = #{ This.rubyforge_project.inspect }
135
+ spec.author = #{ This.author.inspect }
136
+ spec.email = #{ This.email.inspect }
137
+ spec.homepage = #{ This.homepage.inspect }
138
+ end
139
+ __
140
+ }
141
+ end
142
+
143
+ Fu.mkdir_p(This.pkgdir)
144
+ gemspec = "#{ lib }.gemspec"
145
+ open(gemspec, "w"){|fd| fd.puts(template)}
146
+ This.gemspec = gemspec
147
+ end
148
+
149
+ task :gem => [:clean, :gemspec] do
150
+ Fu.mkdir_p(This.pkgdir)
151
+ before = Dir['*.gem']
152
+ cmd = "gem build #{ This.gemspec }"
153
+ `#{ cmd }`
154
+ after = Dir['*.gem']
155
+ gem = ((after - before).first || after.first) or abort('no gem!')
156
+ Fu.mv(gem, This.pkgdir)
157
+ This.gem = File.join(This.pkgdir, File.basename(gem))
158
+ end
159
+
160
+ task :readme do
161
+ samples = ''
162
+ prompt = '~ > '
163
+ lib = This.lib
164
+ version = This.version
165
+
166
+ Dir['sample*/*'].sort.each do |sample|
167
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
168
+
169
+ cmd = "cat #{ sample }"
170
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
171
+ samples << Util.indent(`#{ cmd }`, 4) << "\n"
172
+
173
+ cmd = "ruby #{ sample }"
174
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
175
+
176
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
177
+ samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
178
+ end
179
+
180
+ template =
181
+ if test(?e, 'readme.erb')
182
+ Template{ IO.read('readme.erb') }
183
+ else
184
+ Template {
185
+ <<-__
186
+ NAME
187
+ #{ lib }
188
+
189
+ DESCRIPTION
190
+
191
+ INSTALL
192
+ gem install #{ lib }
193
+
194
+ SAMPLES
195
+ #{ samples }
196
+ __
197
+ }
198
+ end
199
+
200
+ open("README", "w"){|fd| fd.puts template}
201
+ end
202
+
203
+
204
+ task :clean do
205
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)}
206
+ end
207
+
208
+
209
+ task :release => [:clean, :gemspec, :gem] do
210
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
211
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
212
+ raise "no gems?" if gems.size < 1
213
+
214
+ cmd = "gem push #{ This.gem }"
215
+ puts cmd
216
+ puts
217
+ system(cmd)
218
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
219
+
220
+ cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
221
+ puts cmd
222
+ puts
223
+ system(cmd)
224
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
225
+ end
226
+
227
+
228
+
229
+
230
+
231
+ BEGIN {
232
+ # support for this rakefile
233
+ #
234
+ $VERBOSE = nil
235
+
236
+ require 'ostruct'
237
+ require 'erb'
238
+ require 'fileutils'
239
+ require 'rbconfig'
240
+ require 'pp'
241
+
242
+ # fu shortcut
243
+ #
244
+ Fu = FileUtils
245
+
246
+ # cache a bunch of stuff about this rakefile/environment
247
+ #
248
+ This = OpenStruct.new
249
+
250
+ This.file = File.expand_path(__FILE__)
251
+ This.dir = File.dirname(This.file)
252
+ This.pkgdir = File.join(This.dir, 'pkg')
253
+
254
+ # grok lib
255
+ #
256
+ lib = ENV['LIB']
257
+ unless lib
258
+ lib = File.basename(Dir.pwd).sub(/[-].*$/, '')
259
+ end
260
+ This.lib = lib
261
+
262
+ # grok version
263
+ #
264
+ version = ENV['VERSION']
265
+ unless version
266
+ require "./lib/#{ This.lib }"
267
+ This.name = lib.capitalize
268
+ This.object = eval(This.name)
269
+ version = This.object.send(:version)
270
+ end
271
+ This.version = version
272
+
273
+ # we need to know the name of the lib an it's version
274
+ #
275
+ abort('no lib') unless This.lib
276
+ abort('no version') unless This.version
277
+
278
+ # discover full path to this ruby executable
279
+ #
280
+ c = Config::CONFIG
281
+ bindir = c["bindir"] || c['BINDIR']
282
+ ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
283
+ ruby_ext = c['EXEEXT'] || ''
284
+ ruby = File.join(bindir, (ruby_install_name + ruby_ext))
285
+ This.ruby = ruby
286
+
287
+ # some utils
288
+ #
289
+ module Util
290
+ def indent(s, n = 2)
291
+ s = unindent(s)
292
+ ws = ' ' * n
293
+ s.gsub(%r/^/, ws)
294
+ end
295
+
296
+ def unindent(s)
297
+ indent = nil
298
+ s.each_line do |line|
299
+ next if line =~ %r/^\s*$/
300
+ indent = line[%r/^\s*/] and break
301
+ end
302
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
303
+ end
304
+ extend self
305
+ end
306
+
307
+ # template support
308
+ #
309
+ class Template
310
+ def initialize(&block)
311
+ @block = block
312
+ @template = block.call.to_s
313
+ end
314
+ def expand(b=nil)
315
+ ERB.new(Util.unindent(@template)).result((b||@block).binding)
316
+ end
317
+ alias_method 'to_s', 'expand'
318
+ end
319
+ def Template(*args, &block) Template.new(*args, &block) end
320
+
321
+ # colored console output support
322
+ #
323
+ This.ansi = {
324
+ :clear => "\e[0m",
325
+ :reset => "\e[0m",
326
+ :erase_line => "\e[K",
327
+ :erase_char => "\e[P",
328
+ :bold => "\e[1m",
329
+ :dark => "\e[2m",
330
+ :underline => "\e[4m",
331
+ :underscore => "\e[4m",
332
+ :blink => "\e[5m",
333
+ :reverse => "\e[7m",
334
+ :concealed => "\e[8m",
335
+ :black => "\e[30m",
336
+ :red => "\e[31m",
337
+ :green => "\e[32m",
338
+ :yellow => "\e[33m",
339
+ :blue => "\e[34m",
340
+ :magenta => "\e[35m",
341
+ :cyan => "\e[36m",
342
+ :white => "\e[37m",
343
+ :on_black => "\e[40m",
344
+ :on_red => "\e[41m",
345
+ :on_green => "\e[42m",
346
+ :on_yellow => "\e[43m",
347
+ :on_blue => "\e[44m",
348
+ :on_magenta => "\e[45m",
349
+ :on_cyan => "\e[46m",
350
+ :on_white => "\e[47m"
351
+ }
352
+ def say(phrase, *args)
353
+ options = args.last.is_a?(Hash) ? args.pop : {}
354
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
355
+ keys = options.keys
356
+ keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
357
+
358
+ color = options[:color]
359
+ bold = options.has_key?(:bold)
360
+
361
+ parts = [phrase]
362
+ parts.unshift(This.ansi[color]) if color
363
+ parts.unshift(This.ansi[:bold]) if bold
364
+ parts.push(This.ansi[:clear]) if parts.size > 1
365
+
366
+ method = options[:method] || :puts
367
+
368
+ Kernel.send(method, parts.join)
369
+ end
370
+
371
+ # always run out of the project dir
372
+ #
373
+ Dir.chdir(This.dir)
374
+ }
@@ -0,0 +1,29 @@
1
+ ## demon.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "demon"
6
+ spec.version = "0.0.666"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "demon"
9
+ spec.description = "description: demon kicks the ass"
10
+
11
+ spec.files =
12
+ ["README.md", "Rakefile", "demon.gemspec", "lib", "lib/demon.rb"]
13
+
14
+ spec.executables = []
15
+
16
+ spec.require_path = "lib"
17
+
18
+ spec.test_files = nil
19
+
20
+ ### spec.add_dependency 'lib', '>= version'
21
+ #### spec.add_dependency 'map'
22
+
23
+ spec.extensions.push(*[])
24
+
25
+ spec.rubyforge_project = "codeforpeople"
26
+ spec.author = "Ara T. Howard"
27
+ spec.email = "ara.t.howard@gmail.com"
28
+ spec.homepage = "https://github.com/ahoward/demon"
29
+ end
@@ -0,0 +1,786 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ class Demon
4
+ Version = '0.0.666' unless defined?(Version)
5
+ Load = Kernel.method(:load) unless defined?(Load)
6
+
7
+ class << Demon
8
+ def version
9
+ Demon::Version
10
+ end
11
+
12
+ def libdir(*args, &block)
13
+ @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
14
+ libdir = args.empty? ? @libdir : File.join(@libdir, *args.map{|arg| arg.to_s})
15
+ ensure
16
+ if block
17
+ begin
18
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.first==libdir
19
+ module_eval(&block)
20
+ ensure
21
+ $LOAD_PATH.shift() if $LOAD_PATH.first==libdir
22
+ end
23
+ end
24
+ end
25
+
26
+ def load(*args, &block)
27
+ libdir{ Load.call(*args, &block) }
28
+ end
29
+
30
+ def dependencies
31
+ {
32
+ 'map' => [ 'map' , ' >= 6.0.0' ]
33
+ }
34
+ end
35
+ end
36
+
37
+ begin
38
+ require 'rubygems'
39
+ rescue LoadError
40
+ nil
41
+ end
42
+
43
+ begin
44
+ require 'logging'
45
+ rescue LoadError
46
+ nil
47
+ end
48
+
49
+ if defined?(gem)
50
+ Demon.dependencies.each do |lib, dependency|
51
+ gem(*dependency)
52
+ require(lib)
53
+ end
54
+ end
55
+
56
+ require 'fileutils'
57
+ require 'ostruct'
58
+ require 'rbconfig'
59
+ require 'pathname'
60
+ require 'logger'
61
+ require 'yaml'
62
+ end
63
+
64
+
65
+ class Demon
66
+ def initialize(*args, &block)
67
+ #
68
+ @options = Map.extract_options!(args)
69
+
70
+ #
71
+ @__file__ = (
72
+ @options[:script] or
73
+ @options[:file] or
74
+ @options[:__file__] or
75
+ args.shift or
76
+ (block ? eval('File.expand_path(__FILE__)', block.binding) : nil)
77
+ )
78
+ raise("no __FILE__ groked!") unless @__file__
79
+
80
+ @root = @options[:root]
81
+
82
+ @mode = @options[:mode]
83
+
84
+ #
85
+ @script = File.expand_path(@__file__)
86
+ raise("no script groked!") unless test(?s, @script)
87
+
88
+ #
89
+ @cmdline = generate_cmdline
90
+
91
+ @dirname = File.expand_path(File.dirname(@script))
92
+ @basename = File.basename(@script)
93
+
94
+ @script_root = File.expand_path(File.dirname(@script))
95
+
96
+ #
97
+ rails_root = @script_root
98
+ seems_to_be_a_rails_app = false
99
+
100
+ 42.times do
101
+ seems_to_be_a_rails_app =
102
+ %w( app/controllers app/models app/views config Rakefile ).all? do |subdir|
103
+ test(?e, File.join(rails_root, subdir))
104
+ end
105
+
106
+ if seems_to_be_a_rails_app or rails_root == '/'
107
+ break
108
+ end
109
+
110
+ rails_root = File.expand_path(File.dirname(rails_root))
111
+ end
112
+
113
+ if seems_to_be_a_rails_app
114
+ @rails_root = rails_root
115
+
116
+ @demon_dir = File.join(@rails_root, 'log', 'demon')
117
+ @restart_txt = File.join(@rails_root, 'tmp', 'restart.txt')
118
+
119
+ self.prefix = File.join(@demon_dir, @basename)
120
+
121
+ @root = @rails_root
122
+ else
123
+ @rails_root = false
124
+
125
+ @demon_dir = @root || "#{ @script }.demon"
126
+ @restart_txt = File.join(@demon_dir, 'restart.txt')
127
+
128
+ self.prefix = @demon_dir
129
+
130
+ @root = @demon_dir
131
+ end
132
+
133
+ #
134
+ @signals = []
135
+ @started_at = Time.now
136
+ @sleeping = false
137
+ @ppid = Process.pid
138
+
139
+ #
140
+ STDOUT.sync = true
141
+ STDERR.sync = true
142
+
143
+ self
144
+ end
145
+
146
+ def prefix
147
+ @prefix
148
+ end
149
+
150
+ def prefix=(prefix)
151
+ @prefix = File.expand_path(prefix.to_s)
152
+
153
+ @lock_file = File.join(@prefix, 'lock')
154
+ @log_file = File.join(@prefix, 'log')
155
+ @pid_file = File.join(@prefix, 'pid')
156
+ @cmdline_file = File.join(@prefix, 'cmdline')
157
+ @stdin_file = File.join(@prefix, 'stdin')
158
+ @stdout_file = File.join(@prefix, 'stdout')
159
+ @stderr_file = File.join(@prefix, 'stderr')
160
+
161
+ FileUtils.mkdir_p(@prefix)
162
+
163
+ %w( lock log pid cmdline stdin stdout stderr ).each do |which|
164
+ file = instance_variable_get("@#{ which }_file")
165
+ FileUtils.touch(file)
166
+ end
167
+
168
+ @prefix
169
+ end
170
+
171
+ %w(
172
+
173
+ script
174
+ mode
175
+ dirname
176
+ basename
177
+ root
178
+ script_root
179
+ rails_root
180
+ prefix
181
+ basename_dir
182
+ lock_file
183
+ log_file
184
+ pid_file
185
+ cmdline_file
186
+ restart_txt
187
+ started_at
188
+ signals
189
+
190
+ ).each{|a| attr(a)}
191
+
192
+ def start(which = :start, &block)
193
+ mode = "mode_#{ which }".downcase
194
+ send(mode) if respond_to?(mode)
195
+ run_forever_handling_signals_and_logging_errors!(&block)
196
+ end
197
+
198
+ def Demon.start(*args, &block)
199
+ new(*args).tap{|demon| demon.start(&block)}
200
+ end
201
+
202
+ def run(which = :run, &block)
203
+ mode = "mode_#{ which }".downcase
204
+ send(mode) if respond_to?(mode)
205
+ run_forever_handling_signals_and_logging_errors!(&block)
206
+ end
207
+
208
+ def Demon.run(*args, &block)
209
+ new(*args).tap{|demon| demon.run(&block)}
210
+ end
211
+
212
+ module ::Kernel
213
+ def Demon(*args, &block)
214
+ demon = ::Demon.new(*args, &block)
215
+ mode = demon.mode || ARGV[0] || :run
216
+ demon.run(mode, &block)
217
+ end
218
+ end
219
+
220
+ def run_forever_handling_signals_and_logging_errors!(&block)
221
+ loop do
222
+ catch(:signals) do
223
+ process_signals
224
+
225
+ begin
226
+ block.call() if block
227
+ rescue => e
228
+ logger.error(e)
229
+ ensure
230
+ wait(420) unless $! # NOTE: signals wake this up!
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ def Demon.modes
237
+ instance_methods.grep(/mode_(.*)/).map{|mode| mode.to_s.split('_').last}
238
+ end
239
+
240
+ def mode_modes
241
+ puts Demon.modes.join('|')
242
+ exit(42)
243
+ end
244
+
245
+ def Demon.help
246
+ {
247
+ 'start' => 'start in daemon mode',
248
+ 'run' => 'run in the foreground, but otherwise like a daemon',
249
+ 'stop' => 'stop any currently running daemon',
250
+ 'restart' => 'restart any currently running daemon, or start a new one',
251
+ 'pid' => 'print the pid of the running daemon, iff any',
252
+ 'ping' => 'ensure a daemon is running, start one iff not',
253
+ 'signal' => 'hit the daemon, if any, with SIGUSR2',
254
+ 'tail' => 'tail -F all auxillary files (lock files, logs, etc)',
255
+ 'fuser' => 'report the fuser of any auxillary files (lock files, logs, etc)',
256
+ 'log' => 'display the location of the log file',
257
+ 'root' => 'display the location of the root daemon dir (lock files, logs, etc)',
258
+ 'modes' => 'print all modes, even those without "help"',
259
+ 'help' => 'this message'
260
+ }
261
+ end
262
+
263
+ def mode_help
264
+ puts(Demon.help.to_yaml)
265
+ exit(42)
266
+ end
267
+
268
+ def mode_ping
269
+ pid = Integer(IO.read(@pid_file)) rescue nil
270
+
271
+ if pid
272
+ signaled = false
273
+
274
+ begin
275
+ Process.kill('SIGALRM', pid)
276
+ signaled = true
277
+ rescue Object
278
+ nil
279
+ end
280
+
281
+ if signaled
282
+ STDOUT.puts(pid)
283
+ exit
284
+ end
285
+ end
286
+
287
+ Kernel.exec("#{ @script } start")
288
+ end
289
+
290
+ def mode_run
291
+ lock!(:complain => true)
292
+
293
+ pid!
294
+
295
+ cmdline!
296
+
297
+ trap!
298
+
299
+ boot!
300
+
301
+ logging!
302
+
303
+ log!
304
+ end
305
+
306
+ def mode_start
307
+ lock!(:complain => true)
308
+
309
+ daemonize!{|pid| puts(pid)}
310
+
311
+ redirect_io!
312
+
313
+ pid!
314
+
315
+ cmdline!
316
+
317
+ trap!
318
+
319
+ boot!
320
+
321
+ logging!
322
+
323
+ signal_if_redeployed!
324
+
325
+ log!
326
+ end
327
+
328
+ def mode_restart
329
+ begin
330
+ pid = Integer(IO.read(@pid_file)) rescue nil
331
+ Process.kill('HUP', pid)
332
+ puts "Process #{pid} signaled to restart"
333
+ exit(0)
334
+ rescue
335
+ puts "No running process found. Starting a new one."
336
+ mode_start
337
+ end
338
+ end
339
+
340
+ def mode_pid
341
+ pid = Integer(IO.read(@pid_file)) rescue nil
342
+ if pid
343
+ begin
344
+ Process.kill(0, pid)
345
+ puts(pid)
346
+ exit(0)
347
+ rescue Errno::ESRCH
348
+ exit(1)
349
+ end
350
+ else
351
+ exit(1)
352
+ end
353
+ exit(1)
354
+ end
355
+
356
+ def mode_fuser
357
+ exec("fuser #{ @lock_file.inspect }")
358
+ end
359
+
360
+ def mode_stop
361
+ pid = Integer(IO.read(@pid_file)) rescue nil
362
+ if pid
363
+ alive = true
364
+
365
+ %w( QUIT TERM ).each do |signal|
366
+ begin
367
+ Process.kill(signal, pid)
368
+ rescue Errno::ESRCH
369
+ nil
370
+ end
371
+
372
+ 42.times do
373
+ begin
374
+ Process.kill(0, pid)
375
+ sleep(rand)
376
+ rescue Errno::ESRCH
377
+ alive = false
378
+ puts(pid)
379
+ exit(0)
380
+ end
381
+ end
382
+ end
383
+
384
+ if alive
385
+ begin
386
+ Process.kill(-9, pid)
387
+ sleep(rand)
388
+ rescue Errno::ESRCH
389
+ nil
390
+ end
391
+
392
+ begin
393
+ Process.kill(0, pid)
394
+ rescue Errno::ESRCH
395
+ puts(pid)
396
+ exit(0)
397
+ end
398
+ end
399
+ end
400
+ exit(1)
401
+ end
402
+
403
+ def mode_signal(signal = 'SIGUSR2')
404
+ pid = Integer(IO.read(@pid_file)) rescue nil
405
+ if pid
406
+ Process.kill(signal, pid)
407
+ puts(pid)
408
+ exit(0)
409
+ end
410
+ exit(42)
411
+ end
412
+
413
+ def mode_log
414
+ puts(@log_file)
415
+ exit(42)
416
+ end
417
+
418
+ def mode_root
419
+ puts(@root)
420
+ exit(42)
421
+ end
422
+
423
+ def mode_tail
424
+ system("tail -F #{ @stdout_file.inspect } #{ @stderr_file.inspect } #{ @log_file.inspect }")
425
+ exit(42)
426
+ end
427
+
428
+
429
+ def process_signals
430
+ if signaled?
431
+ signals.uniq.each do |signal|
432
+ case signal.to_s
433
+ when /HUP/i
434
+ logger.info('RESTART - signal')
435
+ restart!
436
+ when /USR1/i
437
+ logger.info('RESTART - deploy')
438
+ restart!
439
+ when /USR2/i
440
+ nil
441
+ when /ALRM/i
442
+ nil
443
+ end
444
+ end
445
+ signals.clear
446
+ end
447
+ end
448
+
449
+ def wait(seconds)
450
+ begin
451
+ @sleeping = true
452
+ Kernel.sleep(seconds)
453
+ ensure
454
+ @sleeping = false
455
+ end
456
+ end
457
+
458
+ def restart!
459
+ exit!(0) if fork
460
+
461
+ logger.info('CMD - %s' % Array(@cmdline).join(' '))
462
+
463
+ unlock!
464
+
465
+ keep_ios(STDIN, STDOUT, STDERR)
466
+
467
+ Kernel.exec(*@cmdline)
468
+ end
469
+
470
+ def boot!
471
+ if @rails_root
472
+ Dir.chdir(@rails_root)
473
+ require File.join(@rails_root, 'config', 'boot')
474
+ require File.join(@rails_root, 'config', 'environment')
475
+ end
476
+ end
477
+
478
+ def lock!(options = {})
479
+ complain = options['complain'] || options[:complain]
480
+ fd = open(@lock_file, 'r+')
481
+ status = fd.flock(File::LOCK_EX|File::LOCK_NB)
482
+
483
+ unless status == 0
484
+ if complain
485
+ pid = Integer(IO.read(@pid_file)) rescue '?'
486
+ warn("instance(#{ pid }) is already running!")
487
+ end
488
+ exit(42)
489
+ end
490
+ @lock = fd # prevent garbage collection from closing the file!
491
+ at_exit{ unlock! }
492
+ end
493
+
494
+ def unlock!
495
+ @lock.flock(File::LOCK_UN|File::LOCK_NB) if @lock
496
+ end
497
+
498
+ def pid!
499
+ open(@pid_file, 'w+') do |fd|
500
+ fd.puts(Process.pid)
501
+ end
502
+ at_exit{ FileUtils.rm_f(@pid_file) }
503
+ end
504
+
505
+ def cmdline!
506
+ open(@cmdline_file, 'w+') do |fd|
507
+ fd.puts(Array(@cmdline).join(' '))
508
+ end
509
+ end
510
+
511
+ def trap!
512
+ %w( SIGHUP SIGALRM SIGUSR1 SIGUSR2 ).each do |signal|
513
+ trap(signal) do |sig|
514
+ signals.push(signal)
515
+ logger.debug("SIGNAL - #{ signal }")
516
+ throw(:signals, signal) if sleeping?
517
+ end
518
+ end
519
+
520
+ trap('SIGQUIT'){ exit(42) }
521
+ trap('SIGTERM'){ exit(42) }
522
+ trap('SIGINT'){ exit(42) }
523
+ end
524
+
525
+ def signal_if_redeployed!
526
+ seconds = production? ? 10 : 1
527
+
528
+ Thread.new do
529
+ Thread.current.abort_on_exception = true
530
+ loop do
531
+ Kernel.sleep(seconds)
532
+ Process.kill(:USR1, Process.pid) if redeployed?
533
+ end
534
+ end
535
+ end
536
+
537
+ def log!
538
+ logger.info("START - #{ Process.pid }")
539
+ at_exit do
540
+ logger.info("STOP - #{ Process.pid }") rescue nil
541
+ end
542
+ end
543
+
544
+ def redeployed?
545
+ t = File.stat(current_path_for(@restart_txt)).mtime rescue @started_at
546
+ t > @started_at
547
+ end
548
+
549
+ def generate_cmdline
550
+ current_script = current_path_for(@script)
551
+ [which_ruby, current_script, 'start']
552
+ end
553
+
554
+ def current_path_for(path)
555
+ path.to_s.gsub(%r|\breleases/\d+\b|, 'current')
556
+ end
557
+
558
+ def which_ruby
559
+ c = ::RbConfig::CONFIG
560
+ ruby = File::join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
561
+ raise "ruby @ #{ ruby } not executable!?" unless test(?e, ruby)
562
+ ruby
563
+ end
564
+
565
+ def cap?(&block)
566
+ realpath = proc do |path|
567
+ begin
568
+ (path.is_a?(Pathname) ? path : Pathname.new(path.to_s)).realpath.to_s
569
+ rescue Errno::ENOENT
570
+ nil
571
+ end
572
+ end
573
+
574
+ cap_root = realpath[@rails_root || @root]
575
+
576
+ shared_path = File.expand_path('../../shared', cap_root)
577
+ cap_path = File.dirname(shared_path)
578
+ shared_public_system_path = File.expand_path('../../shared/system')
579
+ public_path = File.join(cap_root, 'public')
580
+
581
+ public_system_path = File.join(public_path.to_s, 'system')
582
+
583
+ is_cap_deploy =
584
+ test(?e, shared_public_system_path) and
585
+ test(?l, public_system_path) and
586
+ realpath[shared_public_system_path] == realpath[public_system_path]
587
+
588
+ return false unless is_cap_deploy
589
+
590
+ args =
591
+ if block
592
+ [cap_path].slice(block.arity > 0 ? (0 ... block.arity) : (0 .. -1))
593
+ else
594
+ []
595
+ end
596
+ block ? block.call(*args) : cap_path
597
+ end
598
+
599
+ def production?
600
+ if defined?(Rails.env)
601
+ Rails.env.production?
602
+ else
603
+ true
604
+ end
605
+ end
606
+
607
+ def sleeping?(&block)
608
+ if block
609
+ block.call if @sleeping
610
+ else
611
+ @sleeping == true
612
+ end
613
+ end
614
+
615
+ def signaled?
616
+ !signals.empty?
617
+ end
618
+
619
+ def logger
620
+ @logger ||= (
621
+ require 'logger' unless defined?(Logger)
622
+ Logger.new(STDERR)
623
+ )
624
+ end
625
+
626
+ def logger=(logger)
627
+ @logger = logger
628
+ end
629
+
630
+ def logging_errors(&block)
631
+ begin
632
+ block.call()
633
+ rescue SignalException => e
634
+ logger.info(e)
635
+ exit(0)
636
+ rescue => e
637
+ logger.error(e)
638
+ end
639
+ end
640
+
641
+
642
+ # daemonize{|pid| puts "the pid of the daemon is #{ pid }"}
643
+ #
644
+
645
+ def daemonize!(options = {}, &block)
646
+ # optional directory and umask
647
+ #
648
+ chdir = options[:chdir] || options['chdir'] || '.'
649
+ umask = options[:umask] || options['umask'] || 0
650
+
651
+ # drop to the background avoiding the possibility of zombies..
652
+ #
653
+ detach!(&block)
654
+
655
+ # close all open io handles *except* these ones
656
+ #
657
+ keep_ios(STDIN, STDOUT, STDERR, @lock)
658
+
659
+ # sane directory and umask
660
+ #
661
+ Dir::chdir(chdir)
662
+ File::umask(umask)
663
+
664
+ # global daemon flag
665
+ #
666
+ $DAEMON = true
667
+ end
668
+
669
+ def detach!(&block)
670
+ # setup a pipe to relay the grandchild pid through
671
+ #
672
+ a, b = IO.pipe
673
+
674
+ # in the parent we wait for the pid, wait on our child to avoid zombies, and
675
+ # then exit
676
+ #
677
+ if fork
678
+ b.close
679
+ pid = Integer(a.read.strip)
680
+ a.close
681
+ block.call(pid) if block
682
+ Process.waitall
683
+ exit!
684
+ end
685
+
686
+ # the child simply exits so it can be reaped - avoiding zombies. the pipes
687
+ # are inherited in the grandchild
688
+ #
689
+ if fork
690
+ exit!
691
+ end
692
+
693
+ # finally, the grandchild sends it's pid back up the pipe to the parent is
694
+ # aware of the pid
695
+ #
696
+ a.close
697
+ b.puts(Process.pid)
698
+ b.close
699
+
700
+ # might as well nohup too...
701
+ #
702
+ Process::setsid rescue nil
703
+ end
704
+
705
+ def redirect_io!(options = {})
706
+ stdin = options[:stdin] || @stdin_file
707
+ stdout = options[:stdout] || @stdout_file
708
+ stderr = options[:stderr] || @stderr_file
709
+
710
+ {
711
+ STDIN => stdin, STDOUT => stdout, STDERR => stderr
712
+ }.each do |io, file|
713
+ opened = false
714
+
715
+ fd =
716
+ case
717
+ when file.is_a?(IO)
718
+ file
719
+ when file.to_s == 'null'
720
+ opened = true
721
+ open('/dev/null', 'ab+')
722
+ else
723
+ opened = true
724
+ open(file, 'ab+')
725
+ end
726
+
727
+ begin
728
+ fd.sync = true rescue nil
729
+ fd.truncate(0) rescue nil
730
+ io.reopen(fd)
731
+ ensure
732
+ fd.close rescue nil if opened
733
+ end
734
+ end
735
+ end
736
+
737
+ def logging!
738
+ number_rolled = 7
739
+ megabytes = 2 ** 20
740
+ max_size = 42 * megabytes
741
+
742
+ @logger =
743
+ if STDIN.tty?
744
+ if defined?(Logging)
745
+ ::Logging.logger(STDERR)
746
+ else
747
+ ::Logger.new(STDERR)
748
+ end
749
+ else
750
+ if defined?(Logging)
751
+ options = defined?(Lockfile) ? {:safe => true} : {}
752
+ ::Logging.logger(@log_file, number_rolled, max_size, options)
753
+ else
754
+ ::Logger.new(@log_file, number_rolled, max_size)
755
+ end
756
+ end
757
+
758
+ @logger.level = ::Logger::INFO rescue nil if production?
759
+ @logger.level = ::Logger::DEBUG if STDERR.tty?
760
+
761
+ @logger
762
+ end
763
+
764
+ def keep_ios(*ios)
765
+ filenos = []
766
+
767
+ ios.flatten.compact.each do |io|
768
+ begin
769
+ fileno = io.respond_to?(:fileno) ? io.fileno : Integer(io)
770
+ filenos.push(fileno)
771
+ rescue Object
772
+ next
773
+ end
774
+ end
775
+
776
+ ObjectSpace.each_object(IO) do |io|
777
+ begin
778
+ fileno = io.fileno
779
+ next if filenos.include?(fileno)
780
+ io.close unless io.closed?
781
+ rescue Object
782
+ next
783
+ end
784
+ end
785
+ end
786
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: demon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.666
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ara T. Howard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-02 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! 'description: demon kicks the ass'
15
+ email: ara.t.howard@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - Rakefile
22
+ - demon.gemspec
23
+ - lib/demon.rb
24
+ homepage: https://github.com/ahoward/demon
25
+ licenses: []
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project: codeforpeople
44
+ rubygems_version: 1.8.23
45
+ signing_key:
46
+ specification_version: 3
47
+ summary: demon
48
+ test_files: []