navy 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/navy +9 -14
- data/examples/navy.conf.rb +73 -0
- data/lib/navy.rb +1 -0
- data/lib/navy/admiral.rb +38 -50
- data/lib/navy/admiral/orders.rb +22 -0
- data/lib/navy/admiral/speak.rb +12 -0
- data/lib/navy/captain.rb +46 -24
- data/lib/navy/captain/orders.rb +21 -0
- data/lib/navy/captain/speak.rb +8 -0
- data/lib/navy/officer.rb +7 -3
- data/lib/navy/orders.rb +97 -0
- data/lib/navy/rank.rb +14 -2
- data/lib/navy/speak.rb +126 -0
- data/lib/navy/version.rb +1 -1
- metadata +17 -10
data/bin/navy
CHANGED
@@ -24,6 +24,10 @@ op = OptionParser.new("", 24, ' ') do |opts|
|
|
24
24
|
ship_options[:daemonize] = !!d
|
25
25
|
end
|
26
26
|
|
27
|
+
opts.on("-c", "--config-file FILE", "Navy-specific config file") do |f|
|
28
|
+
options[:config_file] = f
|
29
|
+
end
|
30
|
+
|
27
31
|
opts.separator "Common options:"
|
28
32
|
|
29
33
|
opts.on_tail("-h", "--help", "Show this message") do
|
@@ -40,24 +44,15 @@ op = OptionParser.new("", 24, ' ') do |opts|
|
|
40
44
|
|
41
45
|
end
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
unless options[:config_file]
|
48
|
+
$stderr.puts "ERROR: config file required", ''
|
49
|
+
puts op.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
50
|
+
exit 1
|
51
|
+
end
|
48
52
|
|
49
53
|
# app = Mule.builder('config', op)
|
50
54
|
# op = nil
|
51
55
|
|
52
|
-
# if $DEBUG
|
53
|
-
# require 'pp'
|
54
|
-
# pp({
|
55
|
-
# :mule_options => options,
|
56
|
-
# # :app => app,
|
57
|
-
# :daemonize => server_options[:daemonize],
|
58
|
-
# })
|
59
|
-
# end
|
60
|
-
|
61
56
|
if $DEBUG
|
62
57
|
require 'pp'
|
63
58
|
pp({
|
@@ -0,0 +1,73 @@
|
|
1
|
+
preload do |admiral|
|
2
|
+
admiral.logger.warn "admiral preload"
|
3
|
+
end
|
4
|
+
|
5
|
+
before_fork do |admiral, captain|
|
6
|
+
admiral.logger.warn "admiral (#{captain.label}) before_fork"
|
7
|
+
captain.logger.warn "captain=#{captain.label} before_fork"
|
8
|
+
end
|
9
|
+
|
10
|
+
after_fork do |admiral, captain|
|
11
|
+
admiral.logger.warn "admiral (#{captain.label}) after_fork"
|
12
|
+
captain.logger.warn "captain=#{captain.label} after_fork"
|
13
|
+
end
|
14
|
+
|
15
|
+
respawn_limit 15, 5
|
16
|
+
|
17
|
+
pid "/tmp/navy.pid"
|
18
|
+
|
19
|
+
# stderr_path "/tmp/navy-err.log"
|
20
|
+
# stdout_path "/tmp/navy-out.log"
|
21
|
+
|
22
|
+
captain :jack do
|
23
|
+
|
24
|
+
stderr_path "/tmp/navy-jack-err.log"
|
25
|
+
stdout_path "/tmp/navy-jack-out.log"
|
26
|
+
|
27
|
+
preload do |captain|
|
28
|
+
captain.logger.warn "captain=#{captain.label} preload"
|
29
|
+
end
|
30
|
+
|
31
|
+
before_fork do |captain, officer|
|
32
|
+
captain.logger.warn "captain=#{captain.label} before_fork"
|
33
|
+
officer.logger.warn "(#{captain.label}) officer=#{officer.number} before_fork"
|
34
|
+
end
|
35
|
+
|
36
|
+
after_fork do |captain, officer|
|
37
|
+
captain.logger.warn "captain=#{captain.label} after_fork"
|
38
|
+
officer.logger.warn "(#{captain.label}) officer=#{officer.number} after_fork"
|
39
|
+
end
|
40
|
+
|
41
|
+
respawn_limit 15, 5
|
42
|
+
|
43
|
+
officers 2 do |officer|
|
44
|
+
trap(:QUIT) { exit }
|
45
|
+
trap(:TERM) { exit }
|
46
|
+
# raise "HELLO"
|
47
|
+
n = 0
|
48
|
+
loop do
|
49
|
+
Navy.logger.info "#{n} jack called (officer=#{officer.number}) pid: #{officer.officer_pid}"
|
50
|
+
# Navy.logger.info "START_CTX: #{START_CTX.inspect}"
|
51
|
+
# Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
|
52
|
+
# Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
|
53
|
+
sleep 1
|
54
|
+
n += 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
captain :blackbeard do
|
61
|
+
|
62
|
+
pid "/tmp/navy-blackbeard.pid"
|
63
|
+
|
64
|
+
officers 5 do
|
65
|
+
trap(:QUIT) { exit }; trap(:TERM) { exit }; loop { sleep 1 }
|
66
|
+
end
|
67
|
+
# officers 5 do
|
68
|
+
# trap(:QUIT, :TRAP) { exit }
|
69
|
+
|
70
|
+
# nil while true
|
71
|
+
# end
|
72
|
+
|
73
|
+
end
|
data/lib/navy.rb
CHANGED
data/lib/navy/admiral.rb
CHANGED
@@ -2,6 +2,7 @@ class Navy::Admiral < Navy::Rank
|
|
2
2
|
|
3
3
|
# This hash maps PIDs to Captains
|
4
4
|
CAPTAINS = {}
|
5
|
+
RESPAWNS = {}
|
5
6
|
|
6
7
|
SELF_PIPE = []
|
7
8
|
|
@@ -25,60 +26,22 @@ class Navy::Admiral < Navy::Rank
|
|
25
26
|
Dir.pwd
|
26
27
|
end
|
27
28
|
|
28
|
-
attr_accessor :admiral_pid
|
29
|
-
attr_reader :
|
29
|
+
attr_accessor :admiral_pid, :captains, :timeout, :respawn_limit, :respawn_limit_seconds
|
30
|
+
attr_reader :options
|
30
31
|
|
31
32
|
def initialize(options = {})
|
32
|
-
@options
|
33
|
-
@
|
34
|
-
|
33
|
+
@options = options.dup
|
34
|
+
@options[:use_defaults] = true
|
35
|
+
self.orders = Navy::Admiral::Orders.new(self.class, @options)
|
36
|
+
|
35
37
|
self.reexec_pid = 0
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
number: 3,
|
40
|
-
job: ->(*args) {
|
41
|
-
trap(:QUIT) { exit }
|
42
|
-
trap(:TERM) { exit }
|
43
|
-
n = 0
|
44
|
-
loop do
|
45
|
-
# Navy.logger.info "#{n} admin called #{args.inspect}"
|
46
|
-
# Navy.logger.info "START_CTX: #{START_CTX.inspect}"
|
47
|
-
# Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
|
48
|
-
# Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
|
49
|
-
sleep 10
|
50
|
-
n += 1
|
51
|
-
end
|
52
|
-
}
|
53
|
-
},
|
54
|
-
user: {
|
55
|
-
number: 3,
|
56
|
-
job: ->(*args) {
|
57
|
-
trap(:QUIT) { exit }
|
58
|
-
trap(:TERM) { exit }
|
59
|
-
n = 0
|
60
|
-
loop do
|
61
|
-
# Navy.logger.info "#{n} user called #{args.inspect}"
|
62
|
-
# Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
|
63
|
-
# Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
|
64
|
-
sleep 10
|
65
|
-
n += 1
|
66
|
-
end
|
67
|
-
}
|
68
|
-
}
|
69
|
-
}
|
70
|
-
self.after_fork = ->(admiral, captain) do
|
71
|
-
admiral.logger.info("captain=#{captain.label} spawned pid=#{$$}")
|
72
|
-
end
|
73
|
-
self.before_fork = ->(admiral, captain) do
|
74
|
-
admiral.logger.info("captain=#{captain.label} spawning...")
|
75
|
-
end
|
76
|
-
self.before_exec = ->(admiral) do
|
77
|
-
admiral.logger.info("forked child re-executing...")
|
78
|
-
end
|
38
|
+
@ready_pipe = @options.delete(:ready_pipe)
|
39
|
+
|
40
|
+
orders.give!(self, except: [ :stderr_path, :stdout_path ])
|
79
41
|
end
|
80
42
|
|
81
43
|
def start
|
44
|
+
orders.give!(self, only: [ :stderr_path, :stdout_path ])
|
82
45
|
init_self_pipe!
|
83
46
|
QUEUE_SIGS.each do |sig|
|
84
47
|
trap(sig) do
|
@@ -92,6 +55,7 @@ class Navy::Admiral < Navy::Rank
|
|
92
55
|
logger.info "admiral starting"
|
93
56
|
|
94
57
|
self.admiral_pid = $$
|
58
|
+
preload.call(self) if preload
|
95
59
|
spawn_missing_captains
|
96
60
|
self
|
97
61
|
end
|
@@ -113,7 +77,7 @@ class Navy::Admiral < Navy::Rank
|
|
113
77
|
# avoid murdering workers after our master process (or the
|
114
78
|
# machine) comes out of suspend/hibernation
|
115
79
|
if (last_check + @timeout) >= (last_check = Time.now)
|
116
|
-
|
80
|
+
sleep_time = murder_lazy_captains
|
117
81
|
logger.debug("would normally murder lazy captains") if $DEBUG
|
118
82
|
else
|
119
83
|
sleep_time = @timeout/2.0 + 1
|
@@ -206,6 +170,11 @@ class Navy::Admiral < Navy::Rank
|
|
206
170
|
end while true
|
207
171
|
end
|
208
172
|
|
173
|
+
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
174
|
+
def murder_lazy_captains
|
175
|
+
@timeout - 1
|
176
|
+
end
|
177
|
+
|
209
178
|
# reexecutes the START_CTX with a new binary
|
210
179
|
def reexec
|
211
180
|
if reexec_pid > 0
|
@@ -251,10 +220,28 @@ class Navy::Admiral < Navy::Rank
|
|
251
220
|
def spawn_missing_captains
|
252
221
|
captains.each do |label, config|
|
253
222
|
CAPTAINS.value?(label) and next
|
223
|
+
respawns = RESPAWNS[label]
|
224
|
+
if respawns
|
225
|
+
first_respawn = respawns.first
|
226
|
+
respawn_count = respawns.size
|
227
|
+
if respawn_count >= respawn_limit
|
228
|
+
if (diff = Time.now - first_respawn) < respawn_limit_seconds
|
229
|
+
logger.error "captain=#{label} respawn error (#{respawn_count} in #{diff} sec, limit #{respawn_limit} in #{respawn_limit_seconds} sec)"
|
230
|
+
@errored_captains ||= {}
|
231
|
+
@errored_captains[label] = captains.delete(label)
|
232
|
+
proc_name "admiral (error)"
|
233
|
+
break
|
234
|
+
else
|
235
|
+
RESPAWNS[label] = []
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
254
239
|
captain = Navy::Captain.new(self, label, config)
|
255
240
|
before_fork.call(self, captain) if before_fork
|
256
241
|
if pid = fork
|
257
242
|
CAPTAINS[pid] = captain
|
243
|
+
RESPAWNS[label] ||= []
|
244
|
+
RESPAWNS[label].push(Time.now)
|
258
245
|
else
|
259
246
|
after_fork.call(self, captain) if after_fork
|
260
247
|
captain.start.join
|
@@ -295,4 +282,5 @@ class Navy::Admiral < Navy::Rank
|
|
295
282
|
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
296
283
|
end
|
297
284
|
|
298
|
-
end
|
285
|
+
end
|
286
|
+
require 'navy/admiral/orders'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Navy::Admiral::Orders < Navy::Orders
|
2
|
+
|
3
|
+
defaults.merge!({
|
4
|
+
after_fork: ->(admiral, captain) do
|
5
|
+
admiral.logger.info("captain=#{captain.label} spawned pid=#{$$}")
|
6
|
+
end,
|
7
|
+
before_fork: ->(admiral, captain) do
|
8
|
+
admiral.logger.info("captain=#{captain.label} spawning...")
|
9
|
+
end,
|
10
|
+
before_exec: ->(admiral) do
|
11
|
+
admiral.logger.info("forked child re-executing...")
|
12
|
+
end,
|
13
|
+
captains: {},
|
14
|
+
preload: ->(admiral) do
|
15
|
+
admiral.logger.info("admiral preloading...")
|
16
|
+
end,
|
17
|
+
respawn_limit: 100,
|
18
|
+
respawn_limit_seconds: 1.0
|
19
|
+
})
|
20
|
+
|
21
|
+
end
|
22
|
+
require 'navy/admiral/speak'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Navy::Admiral::Speak < Navy::Speak
|
2
|
+
|
3
|
+
def before_exec(*args, &block)
|
4
|
+
set_hook(:before_exec, block_given? ? block : args[0], 1)
|
5
|
+
end
|
6
|
+
|
7
|
+
def captain(label, *args, &block)
|
8
|
+
orders.set[:captains] ||= {}
|
9
|
+
orders.set[:captains][label] = block_given? ? block : args[0]
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/lib/navy/captain.rb
CHANGED
@@ -2,6 +2,7 @@ class Navy::Captain < Navy::Rank
|
|
2
2
|
|
3
3
|
# This hash maps PIDs to Officers
|
4
4
|
OFFICERS = {}
|
5
|
+
RESPAWNS = {}
|
5
6
|
|
6
7
|
SELF_PIPE = []
|
7
8
|
|
@@ -11,24 +12,19 @@ class Navy::Captain < Navy::Rank
|
|
11
12
|
# list of signals we care about and trap in admiral.
|
12
13
|
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
13
14
|
|
14
|
-
attr_accessor :label, :captain_pid, :timeout, :
|
15
|
+
attr_accessor :label, :captain_pid, :timeout, :officer_count, :officer_job, :respawn_limit, :respawn_limit_seconds
|
15
16
|
attr_reader :admiral, :options
|
16
17
|
|
17
|
-
def initialize(admiral, label, options = {})
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
self
|
27
|
-
captain.logger.info("(#{captain.label}) officer=#{officer.number} spawning...")
|
28
|
-
end
|
29
|
-
self.before_exec = ->(captain) do
|
30
|
-
captain.logger.info("forked child re-executing...")
|
31
|
-
end
|
18
|
+
def initialize(admiral, label, config, options = {})
|
19
|
+
@options = options.dup
|
20
|
+
@options[:use_defaults] = true
|
21
|
+
@options[:config_file] = config
|
22
|
+
self.orders = Navy::Captain::Orders.new(self.class, @options)
|
23
|
+
@options.merge!(orders.set)
|
24
|
+
|
25
|
+
@admiral, @label = admiral, label
|
26
|
+
|
27
|
+
orders.give!(self, except: [ :stderr_path, :stdout_path ])
|
32
28
|
end
|
33
29
|
|
34
30
|
def ==(other_label)
|
@@ -36,6 +32,7 @@ class Navy::Captain < Navy::Rank
|
|
36
32
|
end
|
37
33
|
|
38
34
|
def start
|
35
|
+
orders.give!(self, only: [ :stderr_path, :stdout_path ])
|
39
36
|
init_self_pipe!
|
40
37
|
QUEUE_SIGS.each do |sig|
|
41
38
|
trap(sig) do
|
@@ -49,6 +46,7 @@ class Navy::Captain < Navy::Rank
|
|
49
46
|
logger.info "captain[#{label}] starting"
|
50
47
|
|
51
48
|
self.captain_pid = $$
|
49
|
+
preload.call(self) if preload
|
52
50
|
spawn_missing_officers
|
53
51
|
self
|
54
52
|
end
|
@@ -64,10 +62,11 @@ class Navy::Captain < Navy::Rank
|
|
64
62
|
reap_all_officers
|
65
63
|
case SIG_QUEUE.shift
|
66
64
|
when nil
|
65
|
+
# logger.info "captain[#{label}] heartbeat"
|
67
66
|
# avoid murdering workers after our master process (or the
|
68
67
|
# machine) comes out of suspend/hibernation
|
69
68
|
if (last_check + @timeout) >= (last_check = Time.now)
|
70
|
-
|
69
|
+
sleep_time = murder_lazy_officers
|
71
70
|
logger.debug("would normally murder lazy officers") if $DEBUG
|
72
71
|
else
|
73
72
|
sleep_time = @timeout/2.0 + 1
|
@@ -96,9 +95,9 @@ class Navy::Captain < Navy::Rank
|
|
96
95
|
# end
|
97
96
|
when :TTIN
|
98
97
|
respawn = true
|
99
|
-
self.
|
98
|
+
self.officer_count += 1
|
100
99
|
when :TTOU
|
101
|
-
self.
|
100
|
+
self.officer_count -= 1 if self.officer_count > 0
|
102
101
|
when :HUP
|
103
102
|
respawn = true
|
104
103
|
# if config.config_file
|
@@ -158,14 +157,36 @@ class Navy::Captain < Navy::Rank
|
|
158
157
|
end while true
|
159
158
|
end
|
160
159
|
|
160
|
+
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
161
|
+
def murder_lazy_officers
|
162
|
+
@timeout - 1
|
163
|
+
end
|
164
|
+
|
161
165
|
def spawn_missing_officers
|
162
166
|
n = -1
|
163
|
-
until (n += 1) == @
|
167
|
+
until (n += 1) == @officer_count
|
164
168
|
OFFICERS.value?(n) and next
|
165
|
-
|
169
|
+
respawns = RESPAWNS[n]
|
170
|
+
if respawns
|
171
|
+
first_respawn = respawns.first
|
172
|
+
respawn_count = respawns.size
|
173
|
+
if respawn_count >= respawn_limit
|
174
|
+
if (diff = Time.now - first_respawn) < respawn_limit_seconds
|
175
|
+
logger.error "(#{label}) officer=#{n} respawn error (#{respawn_count} in #{diff} sec, limit #{respawn_limit} in #{respawn_limit_seconds} sec)"
|
176
|
+
@officer_count -= 1
|
177
|
+
proc_name "captain[#{label}] (error)"
|
178
|
+
break
|
179
|
+
else
|
180
|
+
RESPAWNS[n] = []
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
officer = Navy::Officer.new(self, n, officer_job)
|
166
185
|
before_fork.call(self, officer) if before_fork
|
167
186
|
if pid = fork
|
168
187
|
OFFICERS[pid] = officer
|
188
|
+
RESPAWNS[n] ||= []
|
189
|
+
RESPAWNS[n].push(Time.now)
|
169
190
|
else
|
170
191
|
after_fork.call(self, officer) if after_fork
|
171
192
|
officer.start
|
@@ -179,10 +200,10 @@ class Navy::Captain < Navy::Rank
|
|
179
200
|
end
|
180
201
|
|
181
202
|
def maintain_officer_count
|
182
|
-
(off = OFFICERS.size - @
|
203
|
+
(off = OFFICERS.size - @officer_count) == 0 and return
|
183
204
|
off < 0 and return spawn_missing_officers
|
184
205
|
OFFICERS.dup.each_pair { |opid,o|
|
185
|
-
o.number >= @
|
206
|
+
o.number >= @officer_count and kill_officer(:QUIT, opid) rescue nil
|
186
207
|
}
|
187
208
|
end
|
188
209
|
|
@@ -206,4 +227,5 @@ class Navy::Captain < Navy::Rank
|
|
206
227
|
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
207
228
|
end
|
208
229
|
|
209
|
-
end
|
230
|
+
end
|
231
|
+
require 'navy/captain/orders'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Navy::Captain::Orders < Navy::Orders
|
2
|
+
|
3
|
+
defaults.merge!({
|
4
|
+
after_fork: ->(captain, officer) do
|
5
|
+
captain.logger.info("(#{captain.label}) officer=#{officer.number} spawned pid=#{$$}")
|
6
|
+
end,
|
7
|
+
before_fork: ->(captain, officer) do
|
8
|
+
captain.logger.info("(#{captain.label}) officer=#{officer.number} spawning...")
|
9
|
+
end,
|
10
|
+
officer_job: -> { trap(:QUIT) { exit }; trap(:TERM) { exit }; loop { sleep 1 } },
|
11
|
+
officer_count: 0,
|
12
|
+
preload: ->(captain) do
|
13
|
+
captain.logger.info("captain=#{captain.label} preloading...")
|
14
|
+
end,
|
15
|
+
respawn_limit: 100,
|
16
|
+
respawn_limit_seconds: 1.0,
|
17
|
+
timeout: 30
|
18
|
+
})
|
19
|
+
|
20
|
+
end
|
21
|
+
require 'navy/captain/speak'
|
data/lib/navy/officer.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
1
|
class Navy::Officer < Navy::Rank
|
2
|
-
attr_accessor :number
|
2
|
+
attr_accessor :number, :officer_pid
|
3
3
|
attr_reader :captain, :job
|
4
4
|
def initialize(captain, number, job)
|
5
5
|
@captain, @number, @job = captain, number, job
|
6
|
-
# self.pid = "/tmp/navy-#{captain.label}-#{number}.pid"
|
7
6
|
end
|
8
7
|
|
9
8
|
def ==(other_number)
|
10
9
|
@number == other_number
|
11
10
|
end
|
12
11
|
|
12
|
+
def logger
|
13
|
+
captain.logger
|
14
|
+
end
|
15
|
+
|
13
16
|
def start
|
17
|
+
self.officer_pid = $$
|
14
18
|
proc_name "(#{captain.label}) officer[#{number}]"
|
15
|
-
job.call
|
19
|
+
(job.respond_to?(:arity) && job.arity == 0) ? job.call : job.call(self)
|
16
20
|
rescue => e
|
17
21
|
logger.error(e) rescue nil
|
18
22
|
exit!
|
data/lib/navy/orders.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
class Navy::Orders
|
2
|
+
|
3
|
+
## class_attribute :defaults ##
|
4
|
+
def self.defaults; nil; end
|
5
|
+
def self.defaults?; !!defaults; end
|
6
|
+
def self.defaults=(val)
|
7
|
+
singleton_class.class_eval do
|
8
|
+
begin
|
9
|
+
if method_defined?(:defaults) || private_method_defined?(:defaults)
|
10
|
+
remove_method(:defaults)
|
11
|
+
end
|
12
|
+
rescue NameError
|
13
|
+
# ignore this
|
14
|
+
end
|
15
|
+
define_method(:defaults) { val }
|
16
|
+
end
|
17
|
+
|
18
|
+
val
|
19
|
+
end
|
20
|
+
def defaults
|
21
|
+
defined?(@defaults) ? @defaults : self.class.defaults
|
22
|
+
end
|
23
|
+
def defaults?
|
24
|
+
!!defaults
|
25
|
+
end
|
26
|
+
attr_writer :defaults
|
27
|
+
|
28
|
+
self.defaults = {
|
29
|
+
timeout: 60,
|
30
|
+
logger: Navy.logger,
|
31
|
+
pid: nil
|
32
|
+
}
|
33
|
+
|
34
|
+
def self.inherited(base)
|
35
|
+
base.defaults = self.defaults.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :rank_class
|
39
|
+
attr_accessor :config_file, :set
|
40
|
+
|
41
|
+
def initialize(rank_class, options = {})
|
42
|
+
@rank_class = rank_class
|
43
|
+
self.set = Hash.new(:unset)
|
44
|
+
self.config_file = options.delete(:config_file)
|
45
|
+
@use_defaults = options.delete(:use_defaults)
|
46
|
+
|
47
|
+
set.merge!(defaults) if @use_defaults
|
48
|
+
options.each { |key, value| salor_mouth.__send__(key, value) }
|
49
|
+
|
50
|
+
reload(false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def reload(merge_defaults = true) #:nodoc:
|
54
|
+
if merge_defaults && @use_defaults
|
55
|
+
set.merge!(defaults) if @use_defaults
|
56
|
+
end
|
57
|
+
sailor_mouth.curse!(config_file) if config_file
|
58
|
+
# instance_eval(File.read(config_file), config_file) if config_file
|
59
|
+
|
60
|
+
# parse_rackup_file
|
61
|
+
|
62
|
+
# RACKUP[:set_listener] and
|
63
|
+
# set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
|
64
|
+
|
65
|
+
# # unicorn_rails creates dirs here after working_directory is bound
|
66
|
+
# after_reload.call if after_reload
|
67
|
+
|
68
|
+
# # working_directory binds immediately (easier error checking that way),
|
69
|
+
# # now ensure any paths we changed are correctly set.
|
70
|
+
# [ :pid, :stderr_path, :stdout_path ].each do |var|
|
71
|
+
# String === (path = set[var]) or next
|
72
|
+
# path = File.expand_path(path)
|
73
|
+
# File.writable?(path) || File.writable?(File.dirname(path)) or \
|
74
|
+
# raise ArgumentError, "directory for #{var}=#{path} not writable"
|
75
|
+
# end
|
76
|
+
end
|
77
|
+
|
78
|
+
def give!(rank, options = {})
|
79
|
+
only = options[:only] || []
|
80
|
+
except = options[:except] || (only.empty? ? [] : set.keys - only)
|
81
|
+
set.each do |key, value|
|
82
|
+
value == :unset and next
|
83
|
+
except.include?(key) and next
|
84
|
+
rank.__send__("#{key}=", value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def sailor_mouth
|
89
|
+
@sailor_mouth ||= rank_class::Speak.new(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
def [](key) # :nodoc:
|
93
|
+
set[key]
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
require 'navy/speak'
|
data/lib/navy/rank.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
class Navy::Rank
|
2
2
|
|
3
|
+
attr_accessor :orders
|
4
|
+
|
3
5
|
attr_accessor :before_fork, :after_fork, :before_exec
|
4
6
|
attr_accessor :reexec_pid
|
5
|
-
attr_reader :pid
|
6
7
|
|
7
8
|
def logger
|
8
|
-
|
9
|
+
@logger ||= orders[:logger]
|
9
10
|
end
|
11
|
+
attr_writer :logger
|
12
|
+
|
13
|
+
attr_reader :options
|
10
14
|
|
11
15
|
# sets the path for the PID file of the master process
|
12
16
|
def pid=(path)
|
@@ -37,6 +41,14 @@ class Navy::Rank
|
|
37
41
|
end
|
38
42
|
@pid = path
|
39
43
|
end
|
44
|
+
attr_reader :pid
|
45
|
+
|
46
|
+
attr_accessor :preload
|
47
|
+
|
48
|
+
def stdout_path=(path); redirect_io($stdout, path); end
|
49
|
+
def stderr_path=(path); redirect_io($stderr, path); end
|
50
|
+
|
51
|
+
attr_accessor :timeout
|
40
52
|
|
41
53
|
private
|
42
54
|
|
data/lib/navy/speak.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# cover your ears...
|
2
|
+
class Navy::Speak
|
3
|
+
|
4
|
+
attr_reader :orders
|
5
|
+
|
6
|
+
def initialize(orders)
|
7
|
+
@orders = orders
|
8
|
+
end
|
9
|
+
|
10
|
+
def curse!(file)
|
11
|
+
String === file and return instance_eval(File.read(file), file)
|
12
|
+
instance_eval(&file)
|
13
|
+
end
|
14
|
+
|
15
|
+
## sailor mouth ##
|
16
|
+
|
17
|
+
def after_fork(*args, &block)
|
18
|
+
set_hook(:after_fork, block_given? ? block : args[0])
|
19
|
+
end
|
20
|
+
|
21
|
+
def before_fork(*args, &block)
|
22
|
+
set_hook(:before_fork, block_given? ? block : args[0])
|
23
|
+
end
|
24
|
+
|
25
|
+
def logger(obj)
|
26
|
+
%w(debug info warn error fatal).each do |m|
|
27
|
+
obj.respond_to?(m) and next
|
28
|
+
raise ArgumentError, "logger=#{obj} does not respond to method=#{m}"
|
29
|
+
end
|
30
|
+
|
31
|
+
orders.set[:logger] = obj
|
32
|
+
end
|
33
|
+
|
34
|
+
def pid(path); set_path(:pid, path); end
|
35
|
+
|
36
|
+
def preload(*args, &block)
|
37
|
+
set_hook(:preload, block_given? ? block : args[0], 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
def respawn_limit(respawns, seconds = 1.0)
|
41
|
+
set_int(:respawn_limit, respawns, 1)
|
42
|
+
orders.set[:respawn_limit_seconds] = seconds
|
43
|
+
end
|
44
|
+
|
45
|
+
def stderr_path(path)
|
46
|
+
set_path(:stderr_path, path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def stdout_path(path)
|
50
|
+
set_path(:stdout_path, path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def timeout(seconds)
|
54
|
+
set_int(:timeout, seconds, 3)
|
55
|
+
# POSIX says 31 days is the smallest allowed maximum timeout for select()
|
56
|
+
max = 30 * 60 * 60 * 24
|
57
|
+
orders.set[:timeout] = seconds > max ? max : seconds
|
58
|
+
end
|
59
|
+
|
60
|
+
def user(user, group = nil)
|
61
|
+
# raises ArgumentError on invalid user/group
|
62
|
+
Etc.getpwnam(user)
|
63
|
+
Etc.getgrnam(group) if group
|
64
|
+
set[:user] = [ user, group ]
|
65
|
+
end
|
66
|
+
|
67
|
+
def working_directory(path)
|
68
|
+
# just let chdir raise errors
|
69
|
+
path = File.expand_path(path)
|
70
|
+
if config_file &&
|
71
|
+
config_file[0] != ?/ &&
|
72
|
+
! File.readable?("#{path}/#{config_file}")
|
73
|
+
raise ArgumentError,
|
74
|
+
"config_file=#{config_file} would not be accessible in" \
|
75
|
+
" working_directory=#{path}"
|
76
|
+
end
|
77
|
+
Dir.chdir(path)
|
78
|
+
Navy::Admiral::START_CTX[:cwd] = ENV["PWD"] = path
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def set_int(var, n, min) #:nodoc:
|
84
|
+
Integer === n or raise ArgumentError, "not an integer: #{var}=#{n.inspect}"
|
85
|
+
n >= min or raise ArgumentError, "too low (< #{min}): #{var}=#{n.inspect}"
|
86
|
+
orders.set[var] = n
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_path(var, path) #:nodoc:
|
90
|
+
case path
|
91
|
+
when NilClass, String
|
92
|
+
orders.set[var] = path
|
93
|
+
else
|
94
|
+
raise ArgumentError
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def check_bool(var, bool) # :nodoc:
|
99
|
+
case bool
|
100
|
+
when true, false
|
101
|
+
return bool
|
102
|
+
end
|
103
|
+
raise ArgumentError, "#{var}=#{bool.inspect} not a boolean"
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_bool(var, bool) #:nodoc:
|
107
|
+
orders.set[var] = check_bool(var, bool)
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_hook(var, my_proc, req_arity = 2) #:nodoc:
|
111
|
+
case my_proc
|
112
|
+
when Proc
|
113
|
+
arity = my_proc.arity
|
114
|
+
(arity == req_arity) or \
|
115
|
+
raise ArgumentError,
|
116
|
+
"#{var}=#{my_proc.inspect} has invalid arity: " \
|
117
|
+
"#{arity} (need #{req_arity})"
|
118
|
+
when NilClass
|
119
|
+
my_proc = orders.defaults[var]
|
120
|
+
else
|
121
|
+
raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}"
|
122
|
+
end
|
123
|
+
orders.set[var] = my_proc
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
data/lib/navy/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: navy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
16
|
-
requirement: &
|
16
|
+
requirement: &70259513239380 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70259513239380
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70259513238660 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70259513238660
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70259513237620 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 2.8.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70259513237620
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: kgio
|
49
|
-
requirement: &
|
49
|
+
requirement: &70259513236820 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '2.6'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70259513236820
|
58
58
|
description: Ruby daemon inspired by Unicorn.
|
59
59
|
email:
|
60
60
|
- potatosaladx@gmail.com
|
@@ -69,12 +69,19 @@ files:
|
|
69
69
|
- README.md
|
70
70
|
- Rakefile
|
71
71
|
- bin/navy
|
72
|
+
- examples/navy.conf.rb
|
72
73
|
- lib/navy.rb
|
73
74
|
- lib/navy/admiral.rb
|
75
|
+
- lib/navy/admiral/orders.rb
|
76
|
+
- lib/navy/admiral/speak.rb
|
74
77
|
- lib/navy/captain.rb
|
78
|
+
- lib/navy/captain/orders.rb
|
79
|
+
- lib/navy/captain/speak.rb
|
75
80
|
- lib/navy/officer.rb
|
81
|
+
- lib/navy/orders.rb
|
76
82
|
- lib/navy/rank.rb
|
77
83
|
- lib/navy/ship.rb
|
84
|
+
- lib/navy/speak.rb
|
78
85
|
- lib/navy/util.rb
|
79
86
|
- lib/navy/version.rb
|
80
87
|
- navy.gemspec
|