navy 0.0.3 → 1.0.0
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/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
|