navy 0.0.1 → 0.0.2
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/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
|