navy 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
- # unless options[:config_file]
44
- # $stderr.puts "ERROR: config file required", ''
45
- # puts op.to_s.gsub(/^.*DEPRECATED.*$/s, '')
46
- # exit 1
47
- # end
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
@@ -22,6 +22,7 @@ module Navy
22
22
  end
23
23
  end
24
24
  require 'navy/util'
25
+ require 'navy/orders'
25
26
  require 'navy/rank'
26
27
  require 'navy/admiral'
27
28
  require 'navy/captain'
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 :captains, :options, :timeout
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 = options.dup
33
- @ready_pipe = @options.delete(:ready_pipe)
34
- @timeout = 60
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
- self.pid = "/tmp/navy.pid"
37
- @captains = {
38
- admin: {
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
- # sleep_time = murder_lazy_workers
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, :number
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
- @admiral, @options = admiral, options.dup
19
- @label = label
20
- @number = @options[:number] || 1
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
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
- # sleep_time = murder_lazy_workers
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.number += 1
98
+ self.officer_count += 1
100
99
  when :TTOU
101
- self.number -= 1 if self.number > 0
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) == @number
167
+ until (n += 1) == @officer_count
164
168
  OFFICERS.value?(n) and next
165
- officer = Navy::Officer.new(self, n, options[:job])
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 - @number) == 0 and return
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 >= @number and kill_officer(:QUIT, opid) rescue nil
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'
@@ -0,0 +1,8 @@
1
+ class Navy::Captain::Speak < Navy::Speak
2
+
3
+ def officers(officer_count = 1, *args, &block)
4
+ orders.set[:officer_count] = officer_count
5
+ orders.set[:officer_job] = block_given? ? block : args[0]
6
+ end
7
+
8
+ end
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!
@@ -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
- Navy.logger
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
@@ -1,3 +1,3 @@
1
1
  module Navy
2
- VERSION = "0.0.3"
2
+ VERSION = "1.0.0"
3
3
  end
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.3
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-04 00:00:00.000000000 Z
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: &70181829400440 !ruby/object:Gem::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: *70181829400440
24
+ version_requirements: *70259513239380
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70181829399700 !ruby/object:Gem::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: *70181829399700
35
+ version_requirements: *70259513238660
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70181829398740 !ruby/object:Gem::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: *70181829398740
46
+ version_requirements: *70259513237620
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: kgio
49
- requirement: &70181829398120 !ruby/object:Gem::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: *70181829398120
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