navy 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/navy/admiral.rb +37 -23
- data/lib/navy/captain.rb +19 -7
- data/lib/navy/officer.rb +1 -0
- data/lib/navy/rank.rb +59 -0
- data/lib/navy/util.rb +70 -0
- data/lib/navy/version.rb +1 -1
- data/lib/navy.rb +1 -0
- metadata +11 -10
data/lib/navy/admiral.rb
CHANGED
@@ -25,7 +25,7 @@ class Navy::Admiral < Navy::Rank
|
|
25
25
|
Dir.pwd
|
26
26
|
end
|
27
27
|
|
28
|
-
attr_accessor :admiral_pid
|
28
|
+
attr_accessor :admiral_pid
|
29
29
|
attr_reader :captains, :options, :timeout
|
30
30
|
|
31
31
|
def initialize(options = {})
|
@@ -33,15 +33,17 @@ class Navy::Admiral < Navy::Rank
|
|
33
33
|
@ready_pipe = @options.delete(:ready_pipe)
|
34
34
|
@timeout = 60
|
35
35
|
self.reexec_pid = 0
|
36
|
+
self.pid = "/tmp/navy.pid"
|
36
37
|
@captains = {
|
37
38
|
admin: {
|
39
|
+
number: 3,
|
38
40
|
job: ->(*args) {
|
39
41
|
trap(:QUIT) { exit }
|
40
42
|
trap(:TERM) { exit }
|
41
43
|
n = 0
|
42
44
|
loop do
|
43
|
-
Navy.logger.info "#{n} admin called #{args.inspect}"
|
44
|
-
Navy.logger.info "START_CTX: #{START_CTX.inspect}"
|
45
|
+
# Navy.logger.info "#{n} admin called #{args.inspect}"
|
46
|
+
# Navy.logger.info "START_CTX: #{START_CTX.inspect}"
|
45
47
|
# Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
|
46
48
|
# Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
|
47
49
|
sleep 10
|
@@ -50,12 +52,13 @@ class Navy::Admiral < Navy::Rank
|
|
50
52
|
}
|
51
53
|
},
|
52
54
|
user: {
|
55
|
+
number: 3,
|
53
56
|
job: ->(*args) {
|
54
57
|
trap(:QUIT) { exit }
|
55
58
|
trap(:TERM) { exit }
|
56
59
|
n = 0
|
57
60
|
loop do
|
58
|
-
Navy.logger.info "#{n} user called #{args.inspect}"
|
61
|
+
# Navy.logger.info "#{n} user called #{args.inspect}"
|
59
62
|
# Navy.logger.info "Navy::Admiral::CAPTAINS: #{Navy::Admiral::CAPTAINS.inspect}"
|
60
63
|
# Navy.logger.info "Navy::Admiral::OFFICERS: #{Navy::Captain::OFFICERS.inspect}"
|
61
64
|
sleep 10
|
@@ -64,6 +67,15 @@ class Navy::Admiral < Navy::Rank
|
|
64
67
|
}
|
65
68
|
}
|
66
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
|
67
79
|
end
|
68
80
|
|
69
81
|
def start
|
@@ -116,7 +128,7 @@ class Navy::Admiral < Navy::Rank
|
|
116
128
|
break
|
117
129
|
when :USR1 # rotate logs
|
118
130
|
logger.info "admiral reopening logs..."
|
119
|
-
|
131
|
+
Navy::Util.reopen_logs
|
120
132
|
logger.info "admiral done reopening logs"
|
121
133
|
kill_each_captain(:USR1)
|
122
134
|
when :USR2 # exec binary, stay alive in case something went wrong
|
@@ -182,7 +194,7 @@ class Navy::Admiral < Navy::Rank
|
|
182
194
|
if reexec_pid == cpid
|
183
195
|
logger.error "reaped #{status.inspect} exec()-ed"
|
184
196
|
self.reexec_pid = 0
|
185
|
-
|
197
|
+
self.pid = pid.chomp('.oldbin') if pid
|
186
198
|
proc_name "admiral"
|
187
199
|
else
|
188
200
|
captain = CAPTAINS.delete(cpid) rescue nil
|
@@ -206,19 +218,19 @@ class Navy::Admiral < Navy::Rank
|
|
206
218
|
end
|
207
219
|
end
|
208
220
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
221
|
+
if pid
|
222
|
+
old_pid = "#{pid}.oldbin"
|
223
|
+
begin
|
224
|
+
self.pid = old_pid # clear the path for a new pid file
|
225
|
+
rescue ArgumentError
|
226
|
+
logger.error "old PID:#{valid_pid?(old_pid)} running with " \
|
227
|
+
"existing pid=#{old_pid}, refusing rexec"
|
228
|
+
return
|
229
|
+
rescue => e
|
230
|
+
logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
|
231
|
+
return
|
232
|
+
end
|
233
|
+
end
|
222
234
|
|
223
235
|
logger.info "reexec admiral"
|
224
236
|
|
@@ -230,7 +242,7 @@ class Navy::Admiral < Navy::Rank
|
|
230
242
|
# exec(command, hash) works in at least 1.9.1+, but will only be
|
231
243
|
# required in 1.9.4/2.0.0 at earliest.
|
232
244
|
logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
233
|
-
|
245
|
+
before_exec.call(self)
|
234
246
|
exec(*cmd)
|
235
247
|
end
|
236
248
|
proc_name 'admiral (old)'
|
@@ -240,9 +252,11 @@ class Navy::Admiral < Navy::Rank
|
|
240
252
|
captains.each do |label, config|
|
241
253
|
CAPTAINS.value?(label) and next
|
242
254
|
captain = Navy::Captain.new(self, label, config)
|
255
|
+
before_fork.call(self, captain) if before_fork
|
243
256
|
if pid = fork
|
244
257
|
CAPTAINS[pid] = captain
|
245
258
|
else
|
259
|
+
after_fork.call(self, captain) if after_fork
|
246
260
|
captain.start.join
|
247
261
|
exit
|
248
262
|
end
|
@@ -261,7 +275,7 @@ class Navy::Admiral < Navy::Rank
|
|
261
275
|
}
|
262
276
|
end
|
263
277
|
|
264
|
-
# delivers a signal to a
|
278
|
+
# delivers a signal to a captain and fails gracefully if the captain
|
265
279
|
# is no longer running.
|
266
280
|
def kill_captain(signal, cpid)
|
267
281
|
logger.warn "admiral sending #{signal} to #{cpid}"
|
@@ -270,9 +284,9 @@ class Navy::Admiral < Navy::Rank
|
|
270
284
|
captain = CAPTAINS.delete(cpid) rescue nil
|
271
285
|
end
|
272
286
|
|
273
|
-
# delivers a signal to each
|
287
|
+
# delivers a signal to each captain
|
274
288
|
def kill_each_captain(signal)
|
275
|
-
CAPTAINS.keys.each { |
|
289
|
+
CAPTAINS.keys.each { |cpid| kill_captain(signal, cpid) }
|
276
290
|
end
|
277
291
|
|
278
292
|
def init_self_pipe!
|
data/lib/navy/captain.rb
CHANGED
@@ -11,14 +11,24 @@ class Navy::Captain < Navy::Rank
|
|
11
11
|
# list of signals we care about and trap in admiral.
|
12
12
|
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
13
13
|
|
14
|
-
attr_accessor :label, :captain_pid, :timeout, :
|
14
|
+
attr_accessor :label, :captain_pid, :timeout, :number
|
15
15
|
attr_reader :admiral, :options
|
16
16
|
|
17
17
|
def initialize(admiral, label, options = {})
|
18
18
|
@admiral, @options = admiral, options.dup
|
19
19
|
@label = label
|
20
|
-
@number = 1
|
20
|
+
@number = @options[:number] || 1
|
21
21
|
@timeout = 15
|
22
|
+
# self.pid = "/tmp/navy-#{label}.pid"
|
23
|
+
self.after_fork = ->(captain, officer) do
|
24
|
+
captain.logger.info("(#{captain.label}) officer=#{officer.number} spawned pid=#{$$}")
|
25
|
+
end
|
26
|
+
self.before_fork = ->(captain, officer) do
|
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
|
22
32
|
end
|
23
33
|
|
24
34
|
def ==(other_label)
|
@@ -66,7 +76,7 @@ class Navy::Captain < Navy::Rank
|
|
66
76
|
break
|
67
77
|
when :USR1 # rotate logs
|
68
78
|
logger.info "captain[#{label}] reopening logs..."
|
69
|
-
|
79
|
+
Navy::Util.reopen_logs
|
70
80
|
logger.info "captaion[#{label}] done reopening logs"
|
71
81
|
kill_each_officer(:USR1)
|
72
82
|
when :WINCH
|
@@ -134,7 +144,7 @@ class Navy::Captain < Navy::Rank
|
|
134
144
|
proc_name "captain[#{label}]"
|
135
145
|
else
|
136
146
|
officer = OFFICERS.delete(opid) rescue nil
|
137
|
-
m = "reaped #{status.inspect} officer=#{officer.number rescue 'unknown'}"
|
147
|
+
m = "reaped #{status.inspect} (#{label}) officer=#{officer.number rescue 'unknown'}"
|
138
148
|
status.success? ? logger.info(m) : logger.error(m)
|
139
149
|
end
|
140
150
|
rescue Errno::ECHILD
|
@@ -147,9 +157,11 @@ class Navy::Captain < Navy::Rank
|
|
147
157
|
until (n += 1) == @number
|
148
158
|
OFFICERS.value?(n) and next
|
149
159
|
officer = Navy::Officer.new(self, n, options[:job])
|
160
|
+
before_fork.call(self, officer) if before_fork
|
150
161
|
if pid = fork
|
151
162
|
OFFICERS[pid] = officer
|
152
163
|
else
|
164
|
+
after_fork.call(self, officer) if after_fork
|
153
165
|
officer.start
|
154
166
|
exit
|
155
167
|
end
|
@@ -168,7 +180,7 @@ class Navy::Captain < Navy::Rank
|
|
168
180
|
}
|
169
181
|
end
|
170
182
|
|
171
|
-
# delivers a signal to a
|
183
|
+
# delivers a signal to a officer and fails gracefully if the officer
|
172
184
|
# is no longer running.
|
173
185
|
def kill_officer(signal, opid)
|
174
186
|
logger.warn "captain[#{label}] sending #{signal} to #{opid}"
|
@@ -177,9 +189,9 @@ class Navy::Captain < Navy::Rank
|
|
177
189
|
officer = OFFICERS.delete(opid) rescue nil
|
178
190
|
end
|
179
191
|
|
180
|
-
# delivers a signal to each
|
192
|
+
# delivers a signal to each officer
|
181
193
|
def kill_each_officer(signal)
|
182
|
-
OFFICERS.keys.each { |
|
194
|
+
OFFICERS.keys.each { |opid| kill_officer(signal, opid) }
|
183
195
|
end
|
184
196
|
|
185
197
|
def init_self_pipe!
|
data/lib/navy/officer.rb
CHANGED
data/lib/navy/rank.rb
CHANGED
@@ -1,10 +1,64 @@
|
|
1
1
|
class Navy::Rank
|
2
|
+
|
3
|
+
attr_accessor :before_fork, :after_fork, :before_exec
|
4
|
+
attr_accessor :reexec_pid
|
5
|
+
attr_reader :pid
|
6
|
+
|
2
7
|
def logger
|
3
8
|
Navy.logger
|
4
9
|
end
|
5
10
|
|
11
|
+
# sets the path for the PID file of the master process
|
12
|
+
def pid=(path)
|
13
|
+
if path
|
14
|
+
if x = valid_pid?(path)
|
15
|
+
return path if pid && path == pid && x == $$
|
16
|
+
if x == reexec_pid && pid =~ /\.oldbin\z/
|
17
|
+
logger.warn("will not set pid=#{path} while reexec-ed "\
|
18
|
+
"child is running PID:#{x}")
|
19
|
+
return
|
20
|
+
end
|
21
|
+
raise ArgumentError, "Already running on PID:#{x} " \
|
22
|
+
"(or pid=#{path} is stale)"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
unlink_pid_safe(pid) if pid
|
26
|
+
|
27
|
+
if path
|
28
|
+
fp = begin
|
29
|
+
tmp = "#{File.dirname(path)}/#{rand}.#$$"
|
30
|
+
File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
|
31
|
+
rescue Errno::EEXIST
|
32
|
+
retry
|
33
|
+
end
|
34
|
+
fp.syswrite("#$$\n")
|
35
|
+
File.rename(fp.path, path)
|
36
|
+
fp.close
|
37
|
+
end
|
38
|
+
@pid = path
|
39
|
+
end
|
40
|
+
|
6
41
|
private
|
7
42
|
|
43
|
+
# unlinks a PID file at given +path+ if it contains the current PID
|
44
|
+
# still potentially racy without locking the directory (which is
|
45
|
+
# non-portable and may interact badly with other programs), but the
|
46
|
+
# window for hitting the race condition is small
|
47
|
+
def unlink_pid_safe(path)
|
48
|
+
(File.read(path).to_i == $$ and File.unlink(path)) rescue nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns a PID if a given path contains a non-stale PID file,
|
52
|
+
# nil otherwise.
|
53
|
+
def valid_pid?(path)
|
54
|
+
wpid = File.read(path).to_i
|
55
|
+
wpid <= 0 and return
|
56
|
+
Process.kill(0, wpid)
|
57
|
+
wpid
|
58
|
+
rescue Errno::ESRCH, Errno::ENOENT, Errno::EPERM
|
59
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
60
|
+
end
|
61
|
+
|
8
62
|
def proc_name(tag)
|
9
63
|
$0 = ([
|
10
64
|
File.basename(Navy::Admiral::START_CTX[0]),
|
@@ -12,4 +66,9 @@ class Navy::Rank
|
|
12
66
|
]).concat(Navy::Admiral::START_CTX[:argv]).join(' ')
|
13
67
|
end
|
14
68
|
|
69
|
+
def redirect_io(io, path)
|
70
|
+
File.open(path, 'ab') { |fp| io.reopen(fp) } if path
|
71
|
+
io.sync = true
|
72
|
+
end
|
73
|
+
|
15
74
|
end
|
data/lib/navy/util.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Navy::Util
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# :stopdoc:
|
8
|
+
def is_log?(fp)
|
9
|
+
append_flags = File::WRONLY | File::APPEND
|
10
|
+
|
11
|
+
! fp.closed? &&
|
12
|
+
fp.sync &&
|
13
|
+
(fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
14
|
+
rescue IOError, Errno::EBADF
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def chown_logs(uid, gid)
|
19
|
+
ObjectSpace.each_object(File) do |fp|
|
20
|
+
fp.chown(uid, gid) if is_log?(fp)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# :startdoc:
|
24
|
+
|
25
|
+
# This reopens ALL logfiles in the process that have been rotated
|
26
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
27
|
+
# A +File+ object is considered for reopening if it is:
|
28
|
+
# 1) opened with the O_APPEND and O_WRONLY flags
|
29
|
+
# 2) the current open file handle does not match its original open path
|
30
|
+
# 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
31
|
+
# Returns the number of files reopened
|
32
|
+
#
|
33
|
+
# In Unicorn 3.5.x and earlier, files must be opened with an absolute
|
34
|
+
# path to be considered a log file.
|
35
|
+
def reopen_logs
|
36
|
+
to_reopen = []
|
37
|
+
nr = 0
|
38
|
+
ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
|
39
|
+
|
40
|
+
to_reopen.each do |fp|
|
41
|
+
orig_st = begin
|
42
|
+
fp.stat
|
43
|
+
rescue IOError, Errno::EBADF
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
begin
|
48
|
+
b = File.stat(fp.path)
|
49
|
+
next if orig_st.ino == b.ino && orig_st.dev == b.dev
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
|
55
|
+
fp.sync = true
|
56
|
+
new_st = fp.stat
|
57
|
+
|
58
|
+
# this should only happen in the master:
|
59
|
+
if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
|
60
|
+
fp.chown(orig_st.uid, orig_st.gid)
|
61
|
+
end
|
62
|
+
|
63
|
+
nr += 1
|
64
|
+
rescue IOError, Errno::EBADF
|
65
|
+
# not much we can do...
|
66
|
+
end
|
67
|
+
end
|
68
|
+
nr
|
69
|
+
end
|
70
|
+
end
|
data/lib/navy/version.rb
CHANGED
data/lib/navy.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: 0.0.2
|
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-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
16
|
-
requirement: &
|
16
|
+
requirement: &70128860157040 !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: *70128860157040
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70128860156580 !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: *70128860156580
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70128860155900 !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: *70128860155900
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: kgio
|
49
|
-
requirement: &
|
49
|
+
requirement: &70128860155160 !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: *70128860155160
|
58
58
|
description: Ruby daemon inspired by Unicorn.
|
59
59
|
email:
|
60
60
|
- potatosaladx@gmail.com
|
@@ -75,6 +75,7 @@ files:
|
|
75
75
|
- lib/navy/officer.rb
|
76
76
|
- lib/navy/rank.rb
|
77
77
|
- lib/navy/ship.rb
|
78
|
+
- lib/navy/util.rb
|
78
79
|
- lib/navy/version.rb
|
79
80
|
- navy.gemspec
|
80
81
|
homepage: https://github.com/potatosalad/navy
|