navy 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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in navy.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Andrew Bennett
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Navy
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'navy'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install navy
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/navy ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'navy/ship'
6
+ require 'optparse'
7
+
8
+ ship_options = { options: {} }
9
+ options = ship_options[:options]
10
+
11
+ op = OptionParser.new("", 24, ' ') do |opts|
12
+ cmd = File.basename($0)
13
+ opts.banner = "Usage: #{cmd} [#{cmd} options]"
14
+
15
+ opts.separator "#{cmd} options:"
16
+
17
+ opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
18
+ ship_options[:daemonize] = !!d
19
+ end
20
+
21
+ opts.separator "Common options:"
22
+
23
+ opts.on_tail("-h", "--help", "Show this message") do
24
+ puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
25
+ exit
26
+ end
27
+
28
+ opts.on_tail("-v", "--version", "Show version") do
29
+ puts "#{cmd} v#{Navy::VERSION}"
30
+ exit
31
+ end
32
+
33
+ opts.parse! ARGV
34
+
35
+ end
36
+
37
+ # unless options[:config_file]
38
+ # $stderr.puts "ERROR: config file required", ''
39
+ # puts op.to_s.gsub(/^.*DEPRECATED.*$/s, '')
40
+ # exit 1
41
+ # end
42
+
43
+ # app = Mule.builder('config', op)
44
+ # op = nil
45
+
46
+ # if $DEBUG
47
+ # require 'pp'
48
+ # pp({
49
+ # :mule_options => options,
50
+ # # :app => app,
51
+ # :daemonize => server_options[:daemonize],
52
+ # })
53
+ # end
54
+
55
+ Navy::Ship.launch!(options) if ship_options[:daemonize]
56
+ Navy::Admiral.new(options).start.join
data/lib/navy.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'navy/version'
2
+
3
+ require 'fcntl'
4
+ require 'etc'
5
+ require 'stringio'
6
+ require 'kgio'
7
+
8
+ require 'forwardable'
9
+ require 'logger'
10
+
11
+ module Navy
12
+ extend self
13
+ def logger
14
+ @logger ||= Logger.new($stderr)
15
+ end
16
+
17
+ def log_error(logger, prefix, exc)
18
+ message = exc.message
19
+ message = message.dump if /[[:cntrl:]]/ =~ message
20
+ logger.error "#{prefix}: #{message} (#{exc.class})"
21
+ exc.backtrace.each { |line| logger.error(line) }
22
+ end
23
+ end
24
+ require 'navy/rank'
25
+ require 'navy/admiral'
26
+ require 'navy/captain'
27
+ require 'navy/officer'
@@ -0,0 +1,284 @@
1
+ class Navy::Admiral < Navy::Rank
2
+
3
+ # This hash maps PIDs to Captains
4
+ CAPTAINS = {}
5
+
6
+ SELF_PIPE = []
7
+
8
+ # signal queue used for self-piping
9
+ SIG_QUEUE = []
10
+
11
+ # list of signals we care about and trap in admiral.
12
+ QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
13
+
14
+ START_CTX = {
15
+ :argv => ARGV.map { |arg| arg.dup },
16
+ 0 => $0.dup,
17
+ }
18
+ # We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
19
+ # and like systems
20
+ START_CTX[:cwd] = begin
21
+ a = File.stat(pwd = ENV['PWD'])
22
+ b = File.stat(Dir.pwd)
23
+ a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
24
+ rescue
25
+ Dir.pwd
26
+ end
27
+
28
+ attr_accessor :admiral_pid, :reexec_pid
29
+ attr_reader :captains, :options, :timeout
30
+
31
+ def initialize(options = {})
32
+ @options = options.dup
33
+ @ready_pipe = @options.delete(:ready_pipe)
34
+ @timeout = 60
35
+ self.reexec_pid = 0
36
+ @captains = {
37
+ admin: {
38
+ job: ->(*args) {
39
+ trap(:QUIT) { exit }
40
+ trap(:TERM) { exit }
41
+ n = 0
42
+ loop do
43
+ Navy.logger.info "#{n} admin called #{args.inspect}"
44
+ Navy.logger.info "START_CTX: #{START_CTX.inspect}"
45
+ # Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
46
+ # Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
47
+ sleep 10
48
+ n += 1
49
+ end
50
+ }
51
+ },
52
+ user: {
53
+ job: ->(*args) {
54
+ trap(:QUIT) { exit }
55
+ trap(:TERM) { exit }
56
+ n = 0
57
+ loop do
58
+ Navy.logger.info "#{n} user called #{args.inspect}"
59
+ # Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
60
+ # Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
61
+ sleep 10
62
+ n += 1
63
+ end
64
+ }
65
+ }
66
+ }
67
+ end
68
+
69
+ def start
70
+ init_self_pipe!
71
+ QUEUE_SIGS.each do |sig|
72
+ trap(sig) do
73
+ logger.warn "admiral received #{sig}"
74
+ SIG_QUEUE << sig
75
+ awaken_admiral
76
+ end
77
+ end
78
+ trap(:CHLD) { awaken_admiral }
79
+
80
+ logger.info "admiral starting"
81
+
82
+ self.admiral_pid = $$
83
+ spawn_missing_captains
84
+ self
85
+ end
86
+
87
+ def join
88
+ respawn = true
89
+ last_check = Time.now
90
+
91
+ proc_name 'admiral'
92
+ logger.info "admiral process ready"
93
+ if @ready_pipe
94
+ @ready_pipe.syswrite($$.to_s)
95
+ @ready_pipe = @ready_pipe.close rescue nil
96
+ end
97
+ begin
98
+ reap_all_captains
99
+ case SIG_QUEUE.shift
100
+ when nil
101
+ # avoid murdering workers after our master process (or the
102
+ # machine) comes out of suspend/hibernation
103
+ if (last_check + @timeout) >= (last_check = Time.now)
104
+ # sleep_time = murder_lazy_workers
105
+ logger.debug("would normally murder lazy captains")
106
+ else
107
+ sleep_time = @timeout/2.0 + 1
108
+ logger.debug("waiting #{sleep_time}s after suspend/hibernation")
109
+ end
110
+ maintain_captain_count if respawn
111
+ admiral_sleep(sleep_time)
112
+ when :QUIT # graceful shutdown
113
+ break
114
+ when :TERM, :INT # immediate shutdown
115
+ stop(false)
116
+ break
117
+ when :USR1 # rotate logs
118
+ logger.info "admiral reopening logs..."
119
+ # Unicorn::Util.reopen_logs
120
+ logger.info "admiral done reopening logs"
121
+ kill_each_captain(:USR1)
122
+ when :USR2 # exec binary, stay alive in case something went wrong
123
+ reexec
124
+ when :WINCH
125
+ # if Unicorn::Configurator::RACKUP[:daemonized]
126
+ # respawn = false
127
+ # logger.info "gracefully stopping all workers"
128
+ # kill_each_worker(:QUIT)
129
+ # self.worker_processes = 0
130
+ # else
131
+ logger.info "SIGWINCH ignored because we're not daemonized"
132
+ # end
133
+ when :TTIN
134
+ respawn = true
135
+ kill_each_captain(:TTIN)
136
+ when :TTOU
137
+ kill_each_captain(:TTOU)
138
+ when :HUP
139
+ respawn = true
140
+ # if config.config_file
141
+ # load_config!
142
+ # else # exec binary and exit if there's no config file
143
+ logger.info "config_file not present, reexecuting binary"
144
+ reexec
145
+ # end
146
+ end
147
+ rescue => e
148
+ Navy.log_error(logger, "admiral loop error", e)
149
+ end while true
150
+ stop # gracefully shutdown all captains on our way out
151
+ logger.info "admiral complete"
152
+ end
153
+
154
+ # Terminates all captains, but does not exit admiral process
155
+ def stop(graceful = true)
156
+ limit = Time.now + timeout
157
+ until CAPTAINS.empty? || Time.now > limit
158
+ kill_each_captain(graceful ? :QUIT : :TERM)
159
+ sleep(0.1)
160
+ reap_all_captains
161
+ end
162
+ kill_each_captain(:KILL)
163
+ end
164
+
165
+ private
166
+
167
+ # wait for a signal hander to wake us up and then consume the pipe
168
+ def admiral_sleep(sec)
169
+ IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
170
+ SELF_PIPE[0].kgio_tryread(11)
171
+ end
172
+
173
+ def awaken_admiral
174
+ SELF_PIPE[1].kgio_trywrite('.') # wakeup admiral process from select
175
+ end
176
+
177
+ # reaps all unreaped captains
178
+ def reap_all_captains
179
+ begin
180
+ cpid, status = Process.waitpid2(-1, Process::WNOHANG)
181
+ cpid or return
182
+ if reexec_pid == cpid
183
+ logger.error "reaped #{status.inspect} exec()-ed"
184
+ self.reexec_pid = 0
185
+ # self.pid = pid.chomp('.oldbin') if pid
186
+ proc_name "admiral"
187
+ else
188
+ captain = CAPTAINS.delete(cpid) rescue nil
189
+ m = "reaped #{status.inspect} captain=#{captain.label rescue 'unknown'}"
190
+ status.success? ? logger.info(m) : logger.error(m)
191
+ end
192
+ rescue Errno::ECHILD
193
+ break
194
+ end while true
195
+ end
196
+
197
+ # reexecutes the START_CTX with a new binary
198
+ def reexec
199
+ if reexec_pid > 0
200
+ begin
201
+ Process.kill(0, reexec_pid)
202
+ logger.error "reexec-ed child already running PID:#{reexec_pid}"
203
+ return
204
+ rescue Errno::ESRCH
205
+ self.reexec_pid = 0
206
+ end
207
+ end
208
+
209
+ # if pid
210
+ # old_pid = "#{pid}.oldbin"
211
+ # begin
212
+ # self.pid = old_pid # clear the path for a new pid file
213
+ # rescue ArgumentError
214
+ # logger.error "old PID:#{valid_pid?(old_pid)} running with " \
215
+ # "existing pid=#{old_pid}, refusing rexec"
216
+ # return
217
+ # rescue => e
218
+ # logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
219
+ # return
220
+ # end
221
+ # end
222
+
223
+ logger.info "reexec admiral"
224
+
225
+ self.reexec_pid = fork do
226
+ # ENV['NAVY_FD'] = listener_fds.keys.join(',')
227
+ Dir.chdir(START_CTX[:cwd])
228
+ cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
229
+
230
+ # exec(command, hash) works in at least 1.9.1+, but will only be
231
+ # required in 1.9.4/2.0.0 at earliest.
232
+ logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
233
+ # before_exec.call(self)
234
+ exec(*cmd)
235
+ end
236
+ proc_name 'admiral (old)'
237
+ end
238
+
239
+ def spawn_missing_captains
240
+ captains.each do |label, config|
241
+ CAPTAINS.value?(label) and next
242
+ captain = Navy::Captain.new(self, label, config)
243
+ if pid = fork
244
+ CAPTAINS[pid] = captain
245
+ else
246
+ captain.start.join
247
+ exit
248
+ end
249
+ end
250
+ self
251
+ rescue => e
252
+ logger.error(e) rescue nil
253
+ exit!
254
+ end
255
+
256
+ def maintain_captain_count
257
+ (off = CAPTAINS.size - captains.size) == 0 and return
258
+ off < 0 and return spawn_missing_captains
259
+ CAPTAINS.dup.each_pair { |cpid,c|
260
+ captains.key?(c.label) or kill_captain(:QUIT, cpid) rescue nil
261
+ }
262
+ end
263
+
264
+ # delivers a signal to a worker and fails gracefully if the worker
265
+ # is no longer running.
266
+ def kill_captain(signal, cpid)
267
+ logger.warn "admiral sending #{signal} to #{cpid}"
268
+ Process.kill(signal, cpid)
269
+ rescue Errno::ESRCH
270
+ captain = CAPTAINS.delete(cpid) rescue nil
271
+ end
272
+
273
+ # delivers a signal to each worker
274
+ def kill_each_captain(signal)
275
+ CAPTAINS.keys.each { |wpid| kill_captain(signal, wpid) }
276
+ end
277
+
278
+ def init_self_pipe!
279
+ SELF_PIPE.each { |io| io.close rescue nil }
280
+ SELF_PIPE.replace(Kgio::Pipe.new)
281
+ SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
282
+ end
283
+
284
+ end
@@ -0,0 +1,191 @@
1
+ class Navy::Captain < Navy::Rank
2
+
3
+ # This hash maps PIDs to Officers
4
+ OFFICERS = {}
5
+
6
+ SELF_PIPE = []
7
+
8
+ # signal queue used for self-piping
9
+ SIG_QUEUE = []
10
+
11
+ # list of signals we care about and trap in admiral.
12
+ QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
13
+
14
+ attr_accessor :label, :captain_pid, :timeout, :reexec_pid, :number
15
+ attr_reader :admiral, :options
16
+
17
+ def initialize(admiral, label, options = {})
18
+ @admiral, @options = admiral, options.dup
19
+ @label = label
20
+ @number = 1
21
+ @timeout = 15
22
+ end
23
+
24
+ def ==(other_label)
25
+ @label == other_label
26
+ end
27
+
28
+ def start
29
+ init_self_pipe!
30
+ QUEUE_SIGS.each { |sig| trap(sig) { SIG_QUEUE << sig; awaken_captain } }
31
+ trap(:CHLD) { awaken_captain }
32
+
33
+ logger.info "captain[#{label}] starting"
34
+
35
+ self.captain_pid = $$
36
+ spawn_missing_officers
37
+ self
38
+ end
39
+
40
+ def join
41
+ respawn = true
42
+ last_check = Time.now
43
+
44
+ proc_name "captain[#{label}]"
45
+ logger.info "captain[#{label}] process ready"
46
+
47
+ begin
48
+ reap_all_officers
49
+ case SIG_QUEUE.shift
50
+ when nil
51
+ # avoid murdering workers after our master process (or the
52
+ # machine) comes out of suspend/hibernation
53
+ if (last_check + @timeout) >= (last_check = Time.now)
54
+ # sleep_time = murder_lazy_workers
55
+ logger.debug("would normally murder lazy officers")
56
+ else
57
+ sleep_time = @timeout/2.0 + 1
58
+ logger.debug("waiting #{sleep_time}s after suspend/hibernation")
59
+ end
60
+ maintain_officer_count if respawn
61
+ captain_sleep(sleep_time)
62
+ when :QUIT # graceful shutdown
63
+ break
64
+ when :TERM, :INT # immediate shutdown
65
+ stop(false)
66
+ break
67
+ when :USR1 # rotate logs
68
+ logger.info "captain[#{label}] reopening logs..."
69
+ # Unicorn::Util.reopen_logs
70
+ logger.info "captaion[#{label}] done reopening logs"
71
+ kill_each_officer(:USR1)
72
+ when :WINCH
73
+ # if Unicorn::Configurator::RACKUP[:daemonized]
74
+ # respawn = false
75
+ # logger.info "gracefully stopping all workers"
76
+ # kill_each_worker(:QUIT)
77
+ # self.worker_processes = 0
78
+ # else
79
+ logger.info "SIGWINCH ignored because we're not daemonized"
80
+ # end
81
+ when :TTIN
82
+ respawn = true
83
+ self.number += 1
84
+ when :TTOU
85
+ self.number -= 1 if self.number > 0
86
+ when :HUP
87
+ respawn = true
88
+ # if config.config_file
89
+ # load_config!
90
+ # else # exec binary and exit if there's no config file
91
+ logger.info "config_file not present, reexecuting binary"
92
+ reexec
93
+ # end
94
+ end
95
+ rescue => e
96
+ Navy.log_error(logger, "captain[#{label}] loop error", e)
97
+ end while true
98
+ stop # gracefully shutdown all captains on our way out
99
+ logger.info "captain[#{label}] complete"
100
+ end
101
+
102
+ # Terminates all captains, but does not exit admiral process
103
+ def stop(graceful = true)
104
+ limit = Time.now + timeout
105
+ until OFFICERS.empty? || Time.now > limit
106
+ kill_each_officer(graceful ? :QUIT : :TERM)
107
+ sleep(0.1)
108
+ reap_all_officers
109
+ end
110
+ kill_each_officer(:KILL)
111
+ end
112
+
113
+ private
114
+
115
+ # wait for a signal hander to wake us up and then consume the pipe
116
+ def captain_sleep(sec)
117
+ IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
118
+ SELF_PIPE[0].kgio_tryread(11)
119
+ end
120
+
121
+ def awaken_captain
122
+ SELF_PIPE[1].kgio_trywrite('.') # wakeup captain process from select
123
+ end
124
+
125
+ # reaps all unreaped officers
126
+ def reap_all_officers
127
+ begin
128
+ opid, status = Process.waitpid2(-1, Process::WNOHANG)
129
+ opid or return
130
+ if reexec_pid == opid
131
+ logger.error "reaped #{status.inspect} exec()-ed"
132
+ self.reexec_pid = 0
133
+ # self.pid = pid.chomp('.oldbin') if pid
134
+ proc_name "captain[#{label}]"
135
+ else
136
+ officer = OFFICERS.delete(opid) rescue nil
137
+ m = "reaped #{status.inspect} officer=#{officer.number rescue 'unknown'}"
138
+ status.success? ? logger.info(m) : logger.error(m)
139
+ end
140
+ rescue Errno::ECHILD
141
+ break
142
+ end while true
143
+ end
144
+
145
+ def spawn_missing_officers
146
+ n = -1
147
+ until (n += 1) == @number
148
+ OFFICERS.value?(n) and next
149
+ officer = Navy::Officer.new(self, n, options[:job])
150
+ if pid = fork
151
+ OFFICERS[pid] = officer
152
+ else
153
+ officer.start
154
+ exit
155
+ end
156
+ end
157
+ self
158
+ rescue => e
159
+ logger.error(e) rescue nil
160
+ exit!
161
+ end
162
+
163
+ def maintain_officer_count
164
+ (off = OFFICERS.size - @number) == 0 and return
165
+ off < 0 and return spawn_missing_officers
166
+ OFFICERS.dup.each_pair { |opid,o|
167
+ o.number >= @number and kill_officer(:QUIT, opid) rescue nil
168
+ }
169
+ end
170
+
171
+ # delivers a signal to a worker and fails gracefully if the worker
172
+ # is no longer running.
173
+ def kill_officer(signal, opid)
174
+ logger.warn "captain[#{label}] sending #{signal} to #{opid}"
175
+ Process.kill(signal, opid)
176
+ rescue Errno::ESRCH
177
+ officer = OFFICERS.delete(opid) rescue nil
178
+ end
179
+
180
+ # delivers a signal to each worker
181
+ def kill_each_officer(signal)
182
+ OFFICERS.keys.each { |wpid| kill_officer(signal, wpid) }
183
+ end
184
+
185
+ def init_self_pipe!
186
+ SELF_PIPE.each { |io| io.close rescue nil }
187
+ SELF_PIPE.replace(Kgio::Pipe.new)
188
+ SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
189
+ end
190
+
191
+ end
@@ -0,0 +1,19 @@
1
+ class Navy::Officer < Navy::Rank
2
+ attr_accessor :number
3
+ attr_reader :captain, :job
4
+ def initialize(captain, number, job)
5
+ @captain, @number, @job = captain, number, job
6
+ end
7
+
8
+ def ==(other_number)
9
+ @number == other_number
10
+ end
11
+
12
+ def start
13
+ proc_name "(#{captain.label}) officer[#{number}]"
14
+ job.call
15
+ rescue => e
16
+ logger.error(e) rescue nil
17
+ exit!
18
+ end
19
+ end
data/lib/navy/rank.rb ADDED
@@ -0,0 +1,15 @@
1
+ class Navy::Rank
2
+ def logger
3
+ Navy.logger
4
+ end
5
+
6
+ private
7
+
8
+ def proc_name(tag)
9
+ $0 = ([
10
+ File.basename(Navy::Admiral::START_CTX[0]),
11
+ tag
12
+ ]).concat(Navy::Admiral::START_CTX[:argv]).join(' ')
13
+ end
14
+
15
+ end
data/lib/navy/ship.rb ADDED
@@ -0,0 +1,51 @@
1
+ $stdout.sync = $stderr.sync = true
2
+ $stdin.binmode
3
+ $stdout.binmode
4
+ $stderr.binmode
5
+
6
+ require 'navy'
7
+
8
+ module Navy::Ship
9
+
10
+ extend self
11
+
12
+ def launch!(options)
13
+ # cfg = Mule::Configurator
14
+ $stdin.reopen("/dev/null")
15
+
16
+ # We only start a new process group if we're not being reexecuted
17
+ # and inheriting file descriptors from our parent
18
+ unless ENV['NAVY_FD']
19
+ # grandparent - reads pipe, exits when master is ready
20
+ # \_ parent - exits immediately ASAP
21
+ # \_ unicorn master - writes to pipe when ready
22
+
23
+ rd, wr = IO.pipe
24
+ grandparent = $$
25
+ if fork
26
+ wr.close # grandparent does not write
27
+ else
28
+ rd.close # unicorn master does not read
29
+ Process.setsid
30
+ exit if fork # parent dies now
31
+ end
32
+
33
+ if grandparent == $$
34
+ # this will block until HttpServer#join runs (or it dies)
35
+ admiral_pid = (rd.readpartial(16) rescue nil).to_i
36
+ unless admiral_pid > 1
37
+ warn "admiral failed to start, check stderr log for details"
38
+ exit!(1)
39
+ end
40
+ exit 0
41
+ else # unicorn master process
42
+ options[:ready_pipe] = wr
43
+ end
44
+ end
45
+ # $stderr/$stderr can/will be redirected separately in the Unicorn config
46
+ # cfg::DEFAULTS[:stderr_path] ||= "/dev/null"
47
+ # cfg::DEFAULTS[:stdout_path] ||= "/dev/null"
48
+ # cfg::SERVER[:daemonized] = true
49
+ end
50
+
51
+ end
@@ -0,0 +1,3 @@
1
+ module Navy
2
+ VERSION = "0.0.1"
3
+ end
data/navy.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/navy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Andrew Bennett"]
6
+ gem.email = ["potatosaladx@gmail.com"]
7
+ gem.description = %q{Ruby daemon inspired by Unicorn.}
8
+ gem.summary = %q{Ruby daemon inspired by Unicorn.}
9
+ gem.homepage = "https://github.com/potatosalad/navy"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "navy"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Navy::VERSION
17
+
18
+ gem.add_development_dependency 'pry'
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'rspec', '~> 2.8.0'
21
+
22
+ gem.add_dependency 'kgio', '~> 2.6'
23
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: navy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Bennett
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pry
16
+ requirement: &70147356922140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70147356922140
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70147356921620 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70147356921620
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70147356920800 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70147356920800
47
+ - !ruby/object:Gem::Dependency
48
+ name: kgio
49
+ requirement: &70147356920060 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.6'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70147356920060
58
+ description: Ruby daemon inspired by Unicorn.
59
+ email:
60
+ - potatosaladx@gmail.com
61
+ executables:
62
+ - navy
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - Gemfile
68
+ - LICENSE
69
+ - README.md
70
+ - Rakefile
71
+ - bin/navy
72
+ - lib/navy.rb
73
+ - lib/navy/admiral.rb
74
+ - lib/navy/captain.rb
75
+ - lib/navy/officer.rb
76
+ - lib/navy/rank.rb
77
+ - lib/navy/ship.rb
78
+ - lib/navy/version.rb
79
+ - navy.gemspec
80
+ homepage: https://github.com/potatosalad/navy
81
+ licenses: []
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.17
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Ruby daemon inspired by Unicorn.
104
+ test_files: []
105
+ has_rdoc: