demon 0.0.666

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.
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: []