navy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: