unicorn_horn 0.0.1

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Citizen Logistics, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = unicorn_horn
2
+
3
+ An extraction of the process monitoring logic from Unicorn
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2010 Citizen Logistics, Inc. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "unicorn_horn"
8
+ gem.summary = %Q{An extraction of the process monitoring code from Unicorn}
9
+ gem.email = "joe@citizenlogistics.com"
10
+ gem.homepage = "http://github.com/jxe/unicorn_horn"
11
+ gem.authors = ["Joe Edelman"]
12
+ end
13
+ Jeweler::GemcutterTasks.new
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
16
+ end
17
+
18
+
19
+ require 'rake/rdoctask'
20
+ Rake::RDocTask.new do |rdoc|
21
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
22
+
23
+ rdoc.rdoc_dir = 'rdoc'
24
+ rdoc.title = "unicorn_horn #{version}"
25
+ rdoc.rdoc_files.include('README*')
26
+ rdoc.rdoc_files.include('lib/**/*.rb')
27
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,180 @@
1
+ require 'fcntl'
2
+ require 'tmpdir'
3
+
4
+ module UnicornHorn
5
+ ORIG_ZERO = $0
6
+ ARGVS = ARGV.join(' ')
7
+
8
+ class Worker
9
+ attr_accessor :name, :logger, :idle_timeout
10
+ attr_reader :wpid
11
+ attr_writer :master
12
+
13
+ def initialize name, idle_timeout = 60, &blk
14
+ @name = name
15
+ @idle_timeout = idle_timeout
16
+ @blk = blk
17
+ end
18
+
19
+ def launch!
20
+ @tmp = tmpio
21
+ @wpid = fork do
22
+ $0 = "#{ORIG_ZERO} worker[#{name}] #{ARGVS}"
23
+ @master.forget; @master = nil
24
+ @tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
25
+ [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } }
26
+ alive = @tmp
27
+ m = 0
28
+ logger.info "worker=#{name} ready"
29
+ @blk.call(proc{
30
+ if Process.ppid && alive
31
+ alive.chmod(m = 0 == m ? 1 : 0) or true
32
+ end
33
+ })
34
+ end
35
+ end
36
+
37
+ def tmpio
38
+ fp = File.open("#{Dir::tmpdir}/#{rand}",
39
+ File::RDWR|File::CREAT|File::EXCL, 0600)
40
+ File.unlink(fp.path)
41
+ fp.binmode
42
+ fp.sync = true
43
+ fp
44
+ rescue Errno::EEXIST
45
+ retry
46
+ end
47
+
48
+ def kill_if_idle
49
+ return unless @tmp and @wpid
50
+ stat = @tmp.stat
51
+ stat.mode == 0100600 and return
52
+ @idle_timeout ||= 60
53
+ (diff = (Time.now - stat.ctime)) <= @idle_timeout and return
54
+ @logger.error "worker=#{name} PID:#{@wpid} timeout " \
55
+ "(#{diff}s > #{@idle_timeout}s), killing"
56
+ kill(:KILL)
57
+ end
58
+
59
+ def kill(signal)
60
+ return unless @wpid
61
+ Process.kill(signal, @wpid)
62
+ rescue Errno::ESRCH
63
+ @wpid = nil
64
+ @tmp.close rescue nil
65
+ end
66
+
67
+ def reap(status)
68
+ @wpid = nil
69
+ @tmp.close rescue nil
70
+ m = "reaped #{status.inspect} worker=#{name}"
71
+ status.success? ? @logger.info(m) : @logger.error(m)
72
+ end
73
+ end
74
+
75
+
76
+ class SelfPipeDaemon
77
+ attr_accessor :logger
78
+
79
+ SELF_PIPE = []
80
+ SIG_QUEUE = []
81
+
82
+ def psleep(sec)
83
+ IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
84
+ SELF_PIPE[0].read_nonblock(16*1024, "")
85
+ rescue Errno::EAGAIN, Errno::EINTR
86
+ end
87
+
88
+ def pwake
89
+ SELF_PIPE[1].write_nonblock('.') # wakeup master process from select
90
+ rescue Errno::EAGAIN, Errno::EINTR
91
+ end
92
+
93
+ def register *signals
94
+ @signals = signals
95
+ signals.each { |sig| trap(sig){ |sig_nr| SIG_QUEUE << sig; pwake } }
96
+ end
97
+
98
+ def initialize options = {}
99
+ SELF_PIPE.replace(IO.pipe)
100
+ SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
101
+ options.each_pair{ |k,v| send("#{k}=", v) }
102
+ yield self if block_given?
103
+ self
104
+ end
105
+
106
+ def ploop
107
+ $0 = "#{ORIG_ZERO} master #{ARGVS}"
108
+ logger.info "master process ready"
109
+
110
+ begin
111
+ yield SIG_QUEUE.shift
112
+ rescue => e
113
+ logger.error "Unhandled master loop exception #{e.inspect}."
114
+ logger.error e.backtrace.join("\n")
115
+ end while true
116
+
117
+ logger.info "master complete"
118
+ end
119
+
120
+ def forget
121
+ @signals.each { |sig| trap(sig, nil) }
122
+ SIG_QUEUE.clear
123
+ SELF_PIPE.each { |io| io.close rescue nil }
124
+ end
125
+ end
126
+
127
+
128
+
129
+ class Monitor < SelfPipeDaemon
130
+ attr_accessor :workers, :kill_timeout
131
+
132
+ def start
133
+ workers.each{ |w| w.master = self; w.logger = logger }
134
+ register :QUIT, :INT, :TERM, :CHLD
135
+ workers.each(&:launch!)
136
+
137
+ ploop do |signal|
138
+ reap
139
+ case signal
140
+ when nil
141
+ workers.each(&:kill_if_idle)
142
+ workers.each{ |w| w.wpid or w.launch! }
143
+ psleep 1
144
+ when :CHLD; next
145
+ when :QUIT; raze(:QUIT); break
146
+ when :TERM, :INT; raze(:TERM); break
147
+ end
148
+ end
149
+ end
150
+
151
+ def forget
152
+ super
153
+ workers.clear
154
+ end
155
+
156
+
157
+ private
158
+
159
+ def reap
160
+ begin
161
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
162
+ wpid or return
163
+ next unless worker = workers.detect{ |w| w.wpid == wpid }
164
+ worker.reap(status)
165
+ rescue Errno::ECHILD
166
+ break
167
+ end while true
168
+ end
169
+
170
+ def raze(sig)
171
+ limit = Time.now + (@kill_timeout ||= 60)
172
+ until workers.empty? || Time.now > limit
173
+ workers.each{ |w| w.kill(sig) }
174
+ sleep(0.1)
175
+ reap
176
+ end
177
+ workers.each{ |w| w.kill(:KILL) }
178
+ end
179
+ end
180
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'unicorn_horn'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ # class TestUnicornHorn < Test::Unit::TestCase
4
+ # should "probably rename this file and start testing for real" do
5
+ # flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ # end
7
+ # end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unicorn_horn
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Joe Edelman
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-07 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: joe@citizenlogistics.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - LICENSE
29
+ - README.rdoc
30
+ files:
31
+ - .document
32
+ - .gitignore
33
+ - LICENSE
34
+ - README.rdoc
35
+ - Rakefile
36
+ - VERSION
37
+ - lib/unicorn_horn.rb
38
+ - test/helper.rb
39
+ - test/test_unicorn_horn.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/jxe/unicorn_horn
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.6
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: An extraction of the process monitoring code from Unicorn
70
+ test_files:
71
+ - test/helper.rb
72
+ - test/test_unicorn_horn.rb