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 CHANGED
@@ -25,7 +25,7 @@ class Navy::Admiral < Navy::Rank
25
25
  Dir.pwd
26
26
  end
27
27
 
28
- attr_accessor :admiral_pid, :reexec_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
- # Unicorn::Util.reopen_logs
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
- # self.pid = pid.chomp('.oldbin') if pid
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
- # 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
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
- # before_exec.call(self)
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 worker and fails gracefully if the worker
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 worker
287
+ # delivers a signal to each captain
274
288
  def kill_each_captain(signal)
275
- CAPTAINS.keys.each { |wpid| kill_captain(signal, wpid) }
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, :reexec_pid, :number
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
- # Unicorn::Util.reopen_logs
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 worker and fails gracefully if the worker
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 worker
192
+ # delivers a signal to each officer
181
193
  def kill_each_officer(signal)
182
- OFFICERS.keys.each { |wpid| kill_officer(signal, wpid) }
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
@@ -3,6 +3,7 @@ class Navy::Officer < Navy::Rank
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"
6
7
  end
7
8
 
8
9
  def ==(other_number)
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
@@ -1,3 +1,3 @@
1
1
  module Navy
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/navy.rb CHANGED
@@ -21,6 +21,7 @@ module Navy
21
21
  exc.backtrace.each { |line| logger.error(line) }
22
22
  end
23
23
  end
24
+ require 'navy/util'
24
25
  require 'navy/rank'
25
26
  require 'navy/admiral'
26
27
  require 'navy/captain'
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.1
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-03 00:00:00.000000000 Z
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: &70147356922140 !ruby/object:Gem::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: *70147356922140
24
+ version_requirements: *70128860157040
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70147356921620 !ruby/object:Gem::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: *70147356921620
35
+ version_requirements: *70128860156580
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70147356920800 !ruby/object:Gem::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: *70147356920800
46
+ version_requirements: *70128860155900
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: kgio
49
- requirement: &70147356920060 !ruby/object:Gem::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: *70147356920060
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