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.
- data/README.md +108 -0
- data/Rakefile +374 -0
- data/demon.gemspec +29 -0
- data/lib/demon.rb +786 -0
- metadata +48 -0
data/README.md
ADDED
@@ -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
|
+
````
|
data/Rakefile
ADDED
@@ -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
|
+
}
|
data/demon.gemspec
ADDED
@@ -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
|
data/lib/demon.rb
ADDED
@@ -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: []
|