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 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