navy 0.0.3 → 1.0.0

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